Merge pull request #46 from Flow-Works/thin-dev-branch

Thin-dev-branch
This commit is contained in:
ThinLiquid 2023-11-11 23:53:41 +00:00 committed by GitHub
commit d35a48cb5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 41520 additions and 6 deletions

View file

@ -60,6 +60,7 @@ Please note we have a code of conduct, please follow it in all your interactions
| Issue/Pull Request template | :newspaper_roll: `:newspaper_roll:` |
| Merge pull request | :knot: `:knot:` |
| Assets | :bento: `:bento:` |
| Spelling mistake | :pencil: `:pencil:` |
### Format

View file

@ -29,5 +29,10 @@
"filer": "^1.4.1",
"prism-code-editor": "^2.0.1",
"uuid": "^9.0.1"
},
"ts-standard": {
"ignore": [
"public"
]
}
}

9
public/uv-sw.js Normal file
View file

@ -0,0 +1,9 @@
importScripts('/uv/uv.sw.js');
const sw = new UVServiceWorker();
self.addEventListener('fetch', event =>
event.respondWith(
sw.fetch(event)
)
);

39304
public/uv/uv.bundle.js Normal file

File diff suppressed because one or more lines are too long

40
public/uv/uv.config.js Normal file
View file

@ -0,0 +1,40 @@
self.xor = {
randomMax: 100,
randomMin: -100,
encode: (str) => {
if (!str) return str
return encodeURIComponent(
str
.toString()
.split('')
.map((char, ind) =>
ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 2) : char
)
.join('')
)
},
decode: (str) => {
if (!str) return str
const [input, ...search] = str.split('?')
return (
decodeURIComponent(input)
.split('')
.map((char, ind) =>
ind % 2 ? String.fromCharCode(char.charCodeAt(0) ^ 2) : char
)
.join('') + (search.length ? '?' + search.join('?') : '')
)
}
}
self.__uv$config = {
prefix: '/service/',
bare: 'https://nebulaproxy.io/bare/',
encodeUrl: self.xor.encode,
decodeUrl: self.xor.decode,
handler: '/uv/uv.handler.js',
bundle: '/uv/uv.bundle.js',
config: '/uv/uv.config.js',
}

1129
public/uv/uv.handler.js Normal file

File diff suppressed because it is too large Load diff

789
public/uv/uv.sw.js Normal file
View file

@ -0,0 +1,789 @@
importScripts('/uv/uv.bundle.js');
importScripts('/uv/uv.config.js');
class UVServiceWorker extends EventEmitter {
constructor(config = __uv$config) {
super();
if (!config.bare) config.bare = '/bare/';
this.addresses = typeof config.bare === 'string' ? [ new URL(config.bare, location) ] : config.bare.map(str => new URL(str, location));
this.headers = {
csp: [
'cross-origin-embedder-policy',
'cross-origin-opener-policy',
'cross-origin-resource-policy',
'content-security-policy',
'content-security-policy-report-only',
'expect-ct',
'feature-policy',
'origin-isolation',
'strict-transport-security',
'upgrade-insecure-requests',
'x-content-type-options',
'x-download-options',
'x-frame-options',
'x-permitted-cross-domain-policies',
'x-powered-by',
'x-xss-protection',
],
forward: [
'accept-encoding',
'connection',
'content-length',
],
};
this.method = {
empty: [
'GET',
'HEAD'
]
};
this.statusCode = {
empty: [
204,
304,
],
};
this.config = config;
this.browser = Ultraviolet.Bowser.getParser(self.navigator.userAgent).getBrowserName();
if (this.browser === 'Firefox') {
this.headers.forward.push('user-agent');
this.headers.forward.push('content-type');
};
};
async fetch({ request }) {
if (!request.url.startsWith(location.origin + (this.config.prefix || '/service/'))) {
return fetch(request);
};
try {
const ultraviolet = new Ultraviolet(this.config);
if (typeof this.config.construct === 'function') {
this.config.construct(ultraviolet, 'service');
};
const db = await ultraviolet.cookie.db();
ultraviolet.meta.origin = location.origin;
ultraviolet.meta.base = ultraviolet.meta.url = new URL(ultraviolet.sourceUrl(request.url));
const requestCtx = new RequestContext(
request,
this,
ultraviolet,
!this.method.empty.includes(request.method.toUpperCase()) ? await request.blob() : null
);
if (ultraviolet.meta.url.protocol === 'blob:') {
requestCtx.blob = true;
requestCtx.base = requestCtx.url = new URL(requestCtx.url.pathname);
};
if (request.referrer && request.referrer.startsWith(location.origin)) {
const referer = new URL(ultraviolet.sourceUrl(request.referrer));
if (requestCtx.headers.origin || ultraviolet.meta.url.origin !== referer.origin && request.mode === 'cors') {
requestCtx.headers.origin = referer.origin;
};
requestCtx.headers.referer = referer.href;
};
const cookies = await ultraviolet.cookie.getCookies(db) || [];
const cookieStr = ultraviolet.cookie.serialize(cookies, ultraviolet.meta, false);
if (this.browser === 'Firefox' && !(request.destination === 'iframe' || request.destination === 'document')) {
requestCtx.forward.shift();
};
if (cookieStr) requestCtx.headers.cookie = cookieStr;
requestCtx.headers.Host = requestCtx.url.host;
const reqEvent = new HookEvent(requestCtx, null, null);
this.emit('request', reqEvent);
if (reqEvent.intercepted) return reqEvent.returnValue;
const response = await fetch(requestCtx.send);
if (response.status === 500) {
return Promise.reject('');
};
const responseCtx = new ResponseContext(requestCtx, response, this);
const resEvent = new HookEvent(responseCtx, null, null);
this.emit('beforemod', resEvent);
if (resEvent.intercepted) return resEvent.returnValue;
for (const name of this.headers.csp) {
if (responseCtx.headers[name]) delete responseCtx.headers[name];
};
if (responseCtx.headers.location) {
responseCtx.headers.location = ultraviolet.rewriteUrl(responseCtx.headers.location);
};
if (responseCtx.headers['set-cookie']) {
Promise.resolve(ultraviolet.cookie.setCookies(responseCtx.headers['set-cookie'], db, ultraviolet.meta)).then(() => {
self.clients.matchAll().then(function (clients){
clients.forEach(function(client){
client.postMessage({
msg: 'updateCookies',
url: ultraviolet.meta.url.href,
});
});
});
});
delete responseCtx.headers['set-cookie'];
};
if (responseCtx.body) {
switch(request.destination) {
case 'script':
case 'worker':
responseCtx.body = `if (!self.__uv && self.importScripts) importScripts('${__uv$config.bundle}', '${__uv$config.config}', '${__uv$config.handler}');\n`;
responseCtx.body += ultraviolet.js.rewrite(
await response.text()
);
break;
case 'style':
responseCtx.body = ultraviolet.rewriteCSS(
await response.text()
);
break;
case 'iframe':
case 'document':
if (isHtml(ultraviolet.meta.url, (responseCtx.headers['content-type'] || ''))) {
responseCtx.body = ultraviolet.rewriteHtml(
await response.text(),
{
document: true ,
injectHead: ultraviolet.createHtmlInject(
this.config.handler,
this.config.bundle,
this.config.config,
ultraviolet.cookie.serialize(cookies, ultraviolet.meta, true),
request.referrer
)
}
);
};
};
};
if (requestCtx.headers.accept === 'text/event-stream') {
responseCtx.headers['content-type'] = 'text/event-stream';
};
this.emit('response', resEvent);
if (resEvent.intercepted) return resEvent.returnValue;
return new Response(responseCtx.body, {
headers: responseCtx.headers,
status: responseCtx.status,
statusText: responseCtx.statusText,
});
} catch(err) {
return new Response(err.toString(), {
status: 500,
});
};
};
getBarerResponse(response) {
const headers = {};
const raw = JSON.parse(response.headers.get('x-bare-headers'));
for (const key in raw) {
headers[key.toLowerCase()] = raw[key];
};
return {
headers,
status: +response.headers.get('x-bare-status'),
statusText: response.headers.get('x-bare-status-text'),
body: !this.statusCode.empty.includes(+response.headers.get('x-bare-status')) ? response.body : null,
};
};
get address() {
return this.addresses[Math.floor(Math.random() * this.addresses.length)];
};
static Ultraviolet = Ultraviolet;
};
self.UVServiceWorker = UVServiceWorker;
class ResponseContext {
constructor(request, response, worker) {
const { headers, status, statusText, body } = !request.blob ? worker.getBarerResponse(response) : {
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries([...response.headers.entries()]),
body: response.body,
};
this.request = request;
this.raw = response;
this.ultraviolet = request.ultraviolet;
this.headers = headers;
this.status = status;
this.statusText = statusText;
this.body = body;
};
get url() {
return this.request.url;
}
get base() {
return this.request.base;
};
set base(val) {
this.request.base = val;
};
};
class RequestContext {
constructor(request, worker, ultraviolet, body = null) {
this.ultraviolet = ultraviolet;
this.request = request;
this.headers = Object.fromEntries([...request.headers.entries()]);
this.method = request.method;
this.forward = [...worker.headers.forward];
this.address = worker.address;
this.body = body || null;
this.redirect = request.redirect;
this.credentials = 'omit';
this.mode = request.mode === 'cors' ? request.mode : 'same-origin';
this.blob = false;
};
get send() {
return new Request((!this.blob ? this.address.href + 'v1/' : 'blob:' + location.origin + this.url.pathname), {
method: this.method,
headers: {
'x-bare-protocol': this.url.protocol,
'x-bare-host': this.url.hostname,
'x-bare-path': this.url.pathname + this.url.search,
'x-bare-port': this.url.port || (this.url.protocol === 'https:' ? '443' : '80'),
'x-bare-headers': JSON.stringify(this.headers),
'x-bare-forward-headers': JSON.stringify(this.forward),
},
redirect: this.redirect,
credentials: this.credentials,
mode: location.origin !== this.address.origin ? 'cors' : this.mode,
body: this.body
});
};
get url() {
return this.ultraviolet.meta.url;
};
set url(val) {
this.ultraviolet.meta.url = val;
};
get base() {
return this.ultraviolet.meta.base;
};
set base(val) {
this.ultraviolet.meta.base = val;
};
}
function isHtml(url, contentType = '') {
return (Ultraviolet.mime.contentType((contentType || url.pathname)) || 'text/html').split(';')[0] === 'text/html';
};
class HookEvent {
#intercepted;
#returnValue;
constructor(data = {}, target = null, that = null) {
this.#intercepted = false;
this.#returnValue = null;
this.data = data;
this.target = target;
this.that = that;
};
get intercepted() {
return this.#intercepted;
};
get returnValue() {
return this.#returnValue;
};
respondWith(input) {
this.#returnValue = input;
this.#intercepted = true;
};
};
var R = typeof Reflect === 'object' ? Reflect : null
var ReflectApply = R && typeof R.apply === 'function'
? R.apply
: function ReflectApply(target, receiver, args) {
return Function.prototype.apply.call(target, receiver, args);
}
var ReflectOwnKeys
if (R && typeof R.ownKeys === 'function') {
ReflectOwnKeys = R.ownKeys
} else if (Object.getOwnPropertySymbols) {
ReflectOwnKeys = function ReflectOwnKeys(target) {
return Object.getOwnPropertyNames(target)
.concat(Object.getOwnPropertySymbols(target));
};
} else {
ReflectOwnKeys = function ReflectOwnKeys(target) {
return Object.getOwnPropertyNames(target);
};
}
function ProcessEmitWarning(warning) {
if (console && console.warn) console.warn(warning);
}
var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {
return value !== value;
}
function EventEmitter() {
EventEmitter.init.call(this);
}
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
var defaultMaxListeners = 10;
function checkListener(listener) {
if (typeof listener !== 'function') {
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
}
}
Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
enumerable: true,
get: function() {
return defaultMaxListeners;
},
set: function(arg) {
if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');
}
defaultMaxListeners = arg;
}
});
EventEmitter.init = function() {
if (this._events === undefined ||
this._events === Object.getPrototypeOf(this)._events) {
this._events = Object.create(null);
this._eventsCount = 0;
}
this._maxListeners = this._maxListeners || undefined;
};
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.');
}
this._maxListeners = n;
return this;
};
function _getMaxListeners(that) {
if (that._maxListeners === undefined)
return EventEmitter.defaultMaxListeners;
return that._maxListeners;
}
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return _getMaxListeners(this);
};
EventEmitter.prototype.emit = function emit(type) {
var args = [];
for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
var doError = (type === 'error');
var events = this._events;
if (events !== undefined)
doError = (doError && events.error === undefined);
else if (!doError)
return false;
// If there is no 'error' event listener then throw.
if (doError) {
var er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}
// At least give some kind of context to the user
var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''));
err.context = er;
throw err; // Unhandled 'error' event
}
var handler = events[type];
if (handler === undefined)
return false;
if (typeof handler === 'function') {
ReflectApply(handler, this, args);
} else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
ReflectApply(listeners[i], this, args);
}
return true;
};
function _addListener(target, type, listener, prepend) {
var m;
var events;
var existing;
checkListener(listener);
events = target._events;
if (events === undefined) {
events = target._events = Object.create(null);
target._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener !== undefined) {
target.emit('newListener', type,
listener.listener ? listener.listener : listener);
// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
events = target._events;
}
existing = events[type];
}
if (existing === undefined) {
// Optimize the case of one listener. Don't need the extra array object.
existing = events[type] = listener;
++target._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] =
prepend ? [listener, existing] : [existing, listener];
// If we've already got an array, just append.
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
// eslint-disable-next-line no-restricted-syntax
var w = new Error('Possible EventEmitter memory leak detected. ' +
existing.length + ' ' + String(type) + ' listeners ' +
'added. Use emitter.setMaxListeners() to ' +
'increase limit');
w.name = 'MaxListenersExceededWarning';
w.emitter = target;
w.type = type;
w.count = existing.length;
ProcessEmitWarning(w);
}
}
return target;
}
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false);
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.prependListener =
function prependListener(type, listener) {
return _addListener(this, type, listener, true);
};
function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return this.listener.apply(this.target, arguments);
}
}
function _onceWrap(target, type, listener) {
var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };
var wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
}
EventEmitter.prototype.once = function once(type, listener) {
checkListener(listener);
this.on(type, _onceWrap(this, type, listener));
return this;
};
EventEmitter.prototype.prependOnceListener =
function prependOnceListener(type, listener) {
checkListener(listener);
this.prependListener(type, _onceWrap(this, type, listener));
return this;
};
// Emits a 'removeListener' event if and only if the listener was removed.
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
var list, events, position, i, originalListener;
checkListener(listener);
events = this._events;
if (events === undefined)
return this;
list = events[type];
if (list === undefined)
return this;
if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0)
this._events = Object.create(null);
else {
delete events[type];
if (events.removeListener)
this.emit('removeListener', type, list.listener || listener);
}
} else if (typeof list !== 'function') {
position = -1;
for (i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || list[i].listener === listener) {
originalListener = list[i].listener;
position = i;
break;
}
}
if (position < 0)
return this;
if (position === 0)
list.shift();
else {
spliceOne(list, position);
}
if (list.length === 1)
events[type] = list[0];
if (events.removeListener !== undefined)
this.emit('removeListener', type, originalListener || listener);
}
return this;
};
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.removeAllListeners =
function removeAllListeners(type) {
var listeners, events, i;
events = this._events;
if (events === undefined)
return this;
// not listening for removeListener, no need to emit
if (events.removeListener === undefined) {
if (arguments.length === 0) {
this._events = Object.create(null);
this._eventsCount = 0;
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0)
this._events = Object.create(null);
else
delete events[type];
}
return this;
}
// emit removeListener for all listeners on all events
if (arguments.length === 0) {
var keys = Object.keys(events);
var key;
for (i = 0; i < keys.length; ++i) {
key = keys[i];
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = Object.create(null);
this._eventsCount = 0;
return this;
}
listeners = events[type];
if (typeof listeners === 'function') {
this.removeListener(type, listeners);
} else if (listeners !== undefined) {
// LIFO order
for (i = listeners.length - 1; i >= 0; i--) {
this.removeListener(type, listeners[i]);
}
}
return this;
};
function _listeners(target, type, unwrap) {
var events = target._events;
if (events === undefined)
return [];
var evlistener = events[type];
if (evlistener === undefined)
return [];
if (typeof evlistener === 'function')
return unwrap ? [evlistener.listener || evlistener] : [evlistener];
return unwrap ?
unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
}
EventEmitter.prototype.listeners = function listeners(type) {
return _listeners(this, type, true);
};
EventEmitter.prototype.rawListeners = function rawListeners(type) {
return _listeners(this, type, false);
};
EventEmitter.listenerCount = function(emitter, type) {
if (typeof emitter.listenerCount === 'function') {
return emitter.listenerCount(type);
} else {
return listenerCount.call(emitter, type);
}
};
EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
var events = this._events;
if (events !== undefined) {
var evlistener = events[type];
if (typeof evlistener === 'function') {
return 1;
} else if (evlistener !== undefined) {
return evlistener.length;
}
}
return 0;
}
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
};
function arrayClone(arr, n) {
var copy = new Array(n);
for (var i = 0; i < n; ++i)
copy[i] = arr[i];
return copy;
}
function spliceOne(list, index) {
for (; index + 1 < list.length; index++)
list[index] = list[index + 1];
list.pop();
}
function unwrapListeners(arr) {
var ret = new Array(arr.length);
for (var i = 0; i < ret.length; ++i) {
ret[i] = arr[i].listener || arr[i];
}
return ret;
}
function once(emitter, name) {
return new Promise(function (resolve, reject) {
function errorListener(err) {
emitter.removeListener(name, resolver);
reject(err);
}
function resolver() {
if (typeof emitter.removeListener === 'function') {
emitter.removeListener('error', errorListener);
}
resolve([].slice.call(arguments));
};
eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });
if (name !== 'error') {
addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true });
}
});
}
function addErrorHandlerIfEventEmitter(emitter, handler, flags) {
if (typeof emitter.on === 'function') {
eventTargetAgnosticAddListener(emitter, 'error', handler, flags);
}
}
function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
if (typeof emitter.on === 'function') {
if (flags.once) {
emitter.once(name, listener);
} else {
emitter.on(name, listener);
}
} else if (typeof emitter.addEventListener === 'function') {
// EventTarget does not have `error` event semantics like Node
// EventEmitters, we do not listen for `error` events here.
emitter.addEventListener(name, function wrapListener(arg) {
// IE does not have builtin `{ once: true }` support so we
// have to do it manually.
if (flags.once) {
emitter.removeEventListener(name, wrapListener);
}
listener(arg);
});
} else {
throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter);
}
}

207
src/apps/browser.ts Normal file
View file

@ -0,0 +1,207 @@
import icon from '../assets/icons/null.png'
import { App } from '../types.ts'
import { FlowWindow } from '../wm.ts'
export default class BrowserApp implements App {
meta = {
name: 'Browser',
description: 'A simple browser app.',
pkg: 'flow.browser',
version: '1.0.0',
icon
}
async open (): Promise<FlowWindow> {
const win = window.wm.createWindow({
title: this.meta.name,
icon: this.meta.icon,
width: 400,
height: 300
})
win.content.style.height = '100%'
win.content.style.display = 'flex'
win.content.style.flexDirection = 'column'
win.content.innerHTML = `
<div style="display: flex;padding: 10px;gap: 10px;">
<div id="tabs-container" style="display: flex;gap: 10px;"></div>
<button class="add">+</button>
</div>
<div class="tools" style="display:flex;gap:10px;align-items:center;">
<i class='back bx bx-left-arrow-alt'></i>
<i class='forward bx bx-right-arrow-alt'></i>
<i class='refresh bx bx-refresh'></i>
<input class="inp" style="border-radius: 15px;flex: 1;background: var(--base);border:none;padding: 0px 16px;height: 30px;">
</div>
<div id="content-container"></div>
<style>
iframe {
width: 100%;
height: 100%;
border: none;
}
#content-container {
flex: 1;
}
.add {
border: none;
background: transparent;
}
#tabs-container > div {
padding: 5px 10px;
}
.active {
background: var(--surface-0);
border-radius: 10px!important;
}
.tools {
background: var(--surface-0);
padding: 10px;
}
</style>
`
class Tab {
active = false
header = document.createElement('div')
iframe: HTMLIFrameElement = document.createElement('iframe')
constructor (url: string) {
this.iframe.src = url
this.iframe.style.display = 'none'
this.header.innerHTML = `
<span class="title">Tab</span>
<span class="close">&times;</sp>
`
}
}
class TabManager {
tabs: Tab[] = []
tabHistory: Tab[] = []
activeTab: Tab
addTab (tab: Tab): void {
this.tabs.push(tab)
this.setActiveTab(tab);
(tab.header.querySelector('.title') as HTMLElement).onclick = (): void => this.setActiveTab(tab);
(tab.header.querySelector('.close') as HTMLElement).onclick = (): void => this.closeTab(tab)
win.content.querySelector('#content-container')?.appendChild(tab.iframe)
win.content.querySelector('#tabs-container')?.appendChild(tab.header)
tab.iframe.onload = () => {
(tab.header.querySelector('.title') as HTMLElement).textContent = tab.iframe.contentDocument?.title as string
if (tab === this.activeTab) (win.content.querySelector('.inp') as HTMLInputElement).value = xor.decode((tab.iframe.contentWindow as Window).location.href.split('/service/')[1])
}
}
closeTab (tab: Tab): void {
tab.header.remove()
tab.iframe.remove()
if (tab.active) {
const lastTab = win.content.querySelector('#tabs-container')?.lastElementChild
if (lastTab !== undefined) (lastTab?.querySelector('.title') as HTMLElement).click()
else this.addTab(new Tab(`/service/${xor.encode('https://google.com')}`))
}
}
setActiveTab (tab: Tab): void {
this.tabs.forEach((tab) => {
if (tab.active) {
tab.active = false
tab.iframe.style.display = 'none'
tab.header.classList.remove('active')
}
})
try { (win.content.querySelector('.inp') as HTMLInputElement).value = xor.decode((tab.iframe.contentWindow as Window).location.href.split('/service/')[1]) } catch (e) { (win.content.querySelector('.inp') as HTMLInputElement).value = 'about:blank' }
tab.active = true
tab.iframe.style.display = 'block'
tab.header.classList.add('active')
this.activeTab = tab
this.tabHistory.push(tab)
}
}
const tabManager = new TabManager()
win.content.querySelector('.inp')?.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === 'Enter') {
tabManager.activeTab.iframe.src = `/service/${xor.encode((win.content.querySelector('.inp') as HTMLInputElement).value)}`
}
})
interface XOR {
randomMax: number
randomMin: number
encode: (str: string) => string
decode: (str: string) => string
}
const xor: XOR = {
randomMax: 100,
randomMin: -100,
encode: (str: string): string => {
return encodeURIComponent(
str
.toString()
.split('')
.map((char, ind): string => {
let indCheck
if (ind % 2 === 0) { indCheck = false } else { indCheck = true }
return indCheck ? String.fromCharCode(char.charCodeAt(0) ^ 2) : char
})
.join('')
)
},
decode: (str: string): string => {
const [input, ...search] = str.split('?')
return (
decodeURIComponent(input)
.split('')
.map((char, ind): string => {
let indCheck
if (ind % 2 === 0) { indCheck = false } else { indCheck = true }
return indCheck ? String.fromCharCode(char.charCodeAt(0) ^ 2) : char
})
.join('') + ((search.length > 0) ? '?' + search.join('?') : '')
)
}
};
(win.content.querySelector('button') as HTMLElement).onclick = () => {
tabManager.addTab(new Tab(`/service/${xor.encode('https://google.com')}`))
}
(win.content.querySelector('.refresh') as HTMLElement).onclick = () => {
tabManager.activeTab.iframe.contentWindow?.location.reload()
}
(win.content.querySelector('.back') as HTMLElement).onclick = () => {
tabManager.activeTab.iframe.contentWindow?.history.back()
}
(win.content.querySelector('.forward') as HTMLElement).onclick = () => {
tabManager.activeTab.iframe.contentWindow?.history.forward()
}
tabManager.addTab(new Tab(`/service/${xor.encode('https://google.com')}`))
return win
}
}

View file

@ -1,9 +1,10 @@
import icon from '../assets/icons/info.png'
import badge from '../assets/badge.png'
import { App, PackageJSON } from '../types.ts'
import { FlowWindow } from '../wm.ts'
export default class SettingsApp implements App {
export default class InfoApp implements App {
meta = {
name: 'Info',
description: 'FlowOS Information.',

View file

@ -2,13 +2,14 @@ import { LoadedApp, LoadedPlugin } from './types.ts'
class Flow {
apps: LoadedApp[] = []
appList = [
appList: string[] = [
'settings',
'music',
'files',
'editor',
'info',
'manager'
'manager',
'browser'
]
plugins: LoadedPlugin[] = []

View file

@ -45,4 +45,8 @@ window.wm = new WM();
window.preloader.setStatus('')
window.preloader.finish()
await navigator.serviceWorker.register('/uv-sw.js', {
scope: '/service/'
})
})().catch(e => console.error)

View file

@ -57,6 +57,7 @@ export class FlowWindow {
element: HTMLElement
private readonly header: HTMLElement
private readonly realContent: HTMLElement
content: HTMLElement
maximized: boolean
@ -109,10 +110,33 @@ export class FlowWindow {
(this.header.querySelector('#max') as HTMLElement).onclick = () => this.toggleMax()
}
this.content = document.createElement('window-content')
this.realContent = document.createElement('window-content')
const shadow = this.realContent.attachShadow({ mode: 'open' })
shadow.innerHTML = `
<style>
@import url(https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css);.bx {font-size: 25px;}
* {
-ms-overflow-style: none;
scrollbar-width: none;
font-family: "Satoshi", sans-serif;
font-weight: 600;
color: var(--text);
}
</style>
`
const shadowBody = document.createElement('body')
shadowBody.style.margin = '0px'
shadowBody.style.height = '100%'
shadow.appendChild(shadowBody)
this.content = shadowBody
console.log(this.content)
this.element.appendChild(this.header)
this.element.appendChild(this.content)
this.element.appendChild(this.realContent)
dragElement(this.element, (document.querySelector('window-area') as HTMLElement))
}