Merge pull request #17 from titaniumnetwork-dev/workerware
Merge workerware support into main branch, as well as a far superior games system 🎉
This commit is contained in:
commit
7e3d3c1a0d
62 changed files with 12762 additions and 9217 deletions
|
|
@ -11,6 +11,7 @@ module.exports = {
|
|||
},
|
||||
rules: {
|
||||
"no-unused-vars": "error",
|
||||
"no-undef": "off",
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
dist
|
||||
node_modules
|
||||
node_modules
|
||||
public
|
||||
|
|
@ -16,8 +16,7 @@
|
|||
<body>
|
||||
<h1>Welcome to nginx!</h1>
|
||||
<p>
|
||||
If you see this page, the nginx web server is successfully installed and working. Further
|
||||
configuration is required. If you are expecting another page, please check your network or
|
||||
If you see this page, the nginx web server is successfully installed and working. Further configuration is required. If you are expecting another page, please check your network or
|
||||
<a href="/" id="rcheck"><b>Refresh this page</b></a>
|
||||
</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { defineConfig } from "astro/config";
|
||||
import node from "@astrojs/node";
|
||||
|
||||
import sitemap from "@astrojs/sitemap";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: "https://aluu.xyz",
|
||||
integrations: [
|
||||
|
|
@ -22,4 +20,11 @@ export default defineConfig({
|
|||
adapter: node({
|
||||
mode: "middleware",
|
||||
}),
|
||||
vite: {
|
||||
server: {
|
||||
watch: {
|
||||
usePolling: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
26
index.js
26
index.js
|
|
@ -1,6 +1,8 @@
|
|||
import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
|
||||
import { epoxyPath } from "@mercuryworkshop/epoxy-transport";
|
||||
import { libcurlPath } from "@mercuryworkshop/libcurl-transport";
|
||||
import { baremuxPath } from "@mercuryworkshop/bare-mux/node";
|
||||
import { bareModulePath } from "@mercuryworkshop/bare-as-module3";
|
||||
import { createBareServer } from "@tomphttp/bare-server-node";
|
||||
import express from "express";
|
||||
import { createServer } from "http";
|
||||
|
|
@ -13,8 +15,9 @@ import { existsSync } from "fs";
|
|||
import dotenv from "dotenv";
|
||||
import cookieParser from "cookie-parser";
|
||||
import wisp from "wisp-server-node";
|
||||
import fetch from "node-fetch";
|
||||
import { masqrCheck } from "./masqr.js";
|
||||
import { handler as ssrHandler } from './dist/server/entry.mjs';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const whiteListedDomains = ["aluu.xyz", "localhost:3000"];
|
||||
|
|
@ -51,6 +54,7 @@ const rammerheadScopes = [
|
|||
];
|
||||
const rammerheadSession = /^\/[a-z0-9]{32}/;
|
||||
const app = express();
|
||||
app.use(ssrHandler);
|
||||
app.use(compression({ threshold: 0, filter: () => true }));
|
||||
app.use(cookieParser());
|
||||
|
||||
|
|
@ -65,6 +69,8 @@ app.use(express.static(path.join(process.cwd(), "build")));
|
|||
app.use("/uv/", express.static(uvPath));
|
||||
app.use("/epoxy/", express.static(epoxyPath));
|
||||
app.use("/libcurl/", express.static(libcurlPath));
|
||||
app.use("/baremux/", express.static(baremuxPath));
|
||||
app.use("/baremod/", express.static(bareModulePath));
|
||||
|
||||
app.use(express.json());
|
||||
app.use(
|
||||
|
|
@ -87,9 +93,21 @@ app.use("/custom-favicon", async (req, res) => {
|
|||
res.set("Content-Type", "image/png");
|
||||
res.send(buffer);
|
||||
} catch {
|
||||
|
||||
res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.use("/blocklist", async (req, res) => {
|
||||
try {
|
||||
const { url } = req.query;
|
||||
const response = await fetch(url).then((r) => r.text());
|
||||
res.set("Content-Type", "text/plain");
|
||||
res.send(response);
|
||||
} catch {
|
||||
res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.use("/", express.static("dist/client/"));
|
||||
app.get("/favicon.ico", (req, res) => {
|
||||
res.sendFile(path.join(process.cwd(), "dist/client/favicon.svg"));
|
||||
|
|
@ -105,9 +123,7 @@ app.get("/search", async (req, res) => {
|
|||
try {
|
||||
const { query } = req.query;
|
||||
|
||||
const response = await fetch(`http://api.duckduckgo.com/ac?q=${query}&format=json`).then(
|
||||
(apiRes) => apiRes.json()
|
||||
);
|
||||
const response = await fetch(`http://api.duckduckgo.com/ac?q=${query}&format=json`).then((apiRes) => apiRes.json());
|
||||
|
||||
res.send(response);
|
||||
} catch (err) {
|
||||
|
|
|
|||
8
masqr.js
8
masqr.js
|
|
@ -1,4 +1,5 @@
|
|||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
const failureFile = fs.readFileSync("Checkfailed.html", "utf8");
|
||||
|
||||
|
|
@ -30,12 +31,7 @@ export async function masqrCheck(config) {
|
|||
const auth = Buffer.from(authheader.split(" ")[1], "base64").toString().split(":");
|
||||
const pass = auth[1];
|
||||
|
||||
const licenseCheck = (
|
||||
await (await fetch(config.licenseServer + pass + "&host=" + req.headers.host)).json()
|
||||
)["status"];
|
||||
console.log(
|
||||
config.licenseServer + pass + "&host=" + req.headers.host + " returned " + licenseCheck
|
||||
);
|
||||
const licenseCheck = (await (await fetch(config.licenseServer + pass + "&host=" + req.headers.host)).json())["status"];
|
||||
if (licenseCheck == "License valid") {
|
||||
res.cookie("authcheck", "true", {
|
||||
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000),
|
||||
|
|
|
|||
3651
package-lock.json
generated
3651
package-lock.json
generated
File diff suppressed because it is too large
Load diff
14
package.json
14
package.json
|
|
@ -13,24 +13,24 @@
|
|||
"dependencies": {
|
||||
"@astrojs/node": "^8.2.5",
|
||||
"@astrojs/sitemap": "^3.1.4",
|
||||
"@mercuryworkshop/bare-mux": "^1.0.9",
|
||||
"@mercuryworkshop/epoxy-transport": "^1.1.0",
|
||||
"@mercuryworkshop/libcurl-transport": "^1.3.1",
|
||||
"@titaniumnetwork-dev/ultraviolet": "^3.1.0",
|
||||
"@mercuryworkshop/bare-as-module3": "^2.2.2",
|
||||
"@mercuryworkshop/bare-mux": "^2.0.2",
|
||||
"@mercuryworkshop/epoxy-transport": "^2.1.3",
|
||||
"@mercuryworkshop/libcurl-transport": "^1.3.6",
|
||||
"@titaniumnetwork-dev/ultraviolet": "^3.2.2",
|
||||
"@tomphttp/bare-server-node": "^2.0.3",
|
||||
"@types/node": "^20.14.10",
|
||||
"astro": "^4.7.0",
|
||||
"chalk": "^5.3.0",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"node-fetch": "^3.3.2",
|
||||
"notyf": "^3.10.0",
|
||||
"rammerhead": "https://github.com/NebulaServices/rammerhead/releases/download/rammerhead-1.2.41-nebula.8/rammerhead-1.2.41-nebula.7.tgz",
|
||||
"wisp-server-node": "^1.0.6"
|
||||
"wisp-server-node": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.24.1",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"eslint": "^8.57.0",
|
||||
|
|
|
|||
9890
pnpm-lock.yaml
generated
9890
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,7 @@ const config = {
|
|||
bracketSameLine: true,
|
||||
arrowParens: "always",
|
||||
plugins: ["prettier-plugin-astro"],
|
||||
printWidth: 100,
|
||||
printWidth: 200,
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -1,589 +0,0 @@
|
|||
//Built from: https://github.com/motortruck1221/bare-as-module3 (commit: 36759f801e0009027878edecff156408b06404c6)
|
||||
(function (global, factory) {
|
||||
typeof exports === "object" && typeof module !== "undefined"
|
||||
? factory(exports)
|
||||
: typeof define === "function" && define.amd
|
||||
? define(["exports"], factory)
|
||||
: ((global = typeof globalThis !== "undefined" ? globalThis : global || self),
|
||||
factory((global.BareMod = {})));
|
||||
})(this, function (exports) {
|
||||
"use strict";
|
||||
|
||||
// The user likely has overwritten all networking functions after importing bare-client
|
||||
// It is our responsibility to make sure components of Bare-Client are using native networking functions
|
||||
// These exports are provided to plugins by @rollup/plugin-inject
|
||||
const fetch = globalThis.fetch;
|
||||
const WebSocket = globalThis.WebSocket;
|
||||
const WebSocketFields = {
|
||||
prototype: {
|
||||
send: WebSocket.prototype.send,
|
||||
},
|
||||
CLOSED: WebSocket.CLOSED,
|
||||
CLOSING: WebSocket.CLOSING,
|
||||
CONNECTING: WebSocket.CONNECTING,
|
||||
OPEN: WebSocket.OPEN,
|
||||
};
|
||||
|
||||
class BareError extends Error {
|
||||
status;
|
||||
body;
|
||||
constructor(status, body) {
|
||||
super(body.message || body.code);
|
||||
this.status = status;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
class Client {
|
||||
base;
|
||||
/**
|
||||
*
|
||||
* @param version Version provided by extension
|
||||
* @param server Bare Server URL provided by BareClient
|
||||
*/
|
||||
constructor(version, server) {
|
||||
this.base = new URL(`./v${version}/`, server);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* JavaScript MD5
|
||||
* Adopted from https://github.com/blueimp/JavaScript-MD5
|
||||
*
|
||||
* Copyright 2011, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* Based on
|
||||
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
|
||||
* Digest Algorithm, as defined in RFC 1321.
|
||||
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
|
||||
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
|
||||
* Distributed under the BSD License
|
||||
* See http://pajhome.org.uk/crypt/md5 for more info.
|
||||
*/
|
||||
/**
|
||||
* Add integers, wrapping at 2^32.
|
||||
* This uses 16-bit operations internally to work around bugs in interpreters.
|
||||
*
|
||||
* @param x First integer
|
||||
* @param y Second integer
|
||||
* @returns Sum
|
||||
*/
|
||||
function safeAdd(x, y) {
|
||||
const lsw = (x & 0xffff) + (y & 0xffff);
|
||||
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
return (msw << 16) | (lsw & 0xffff);
|
||||
}
|
||||
/**
|
||||
* Bitwise rotate a 32-bit number to the left.
|
||||
*
|
||||
* @param num 32-bit number
|
||||
* @param cnt Rotation count
|
||||
* @returns Rotated number
|
||||
*/
|
||||
function bitRotateLeft(num, cnt) {
|
||||
return (num << cnt) | (num >>> (32 - cnt));
|
||||
}
|
||||
/**
|
||||
* Basic operation the algorithm uses.
|
||||
*
|
||||
* @param q q
|
||||
* @param a a
|
||||
* @param b b
|
||||
* @param x x
|
||||
* @param s s
|
||||
* @param t t
|
||||
* @returns Result
|
||||
*/
|
||||
function md5cmn(q, a, b, x, s, t) {
|
||||
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
|
||||
}
|
||||
/**
|
||||
* Basic operation the algorithm uses.
|
||||
*
|
||||
* @param a a
|
||||
* @param b b
|
||||
* @param c c
|
||||
* @param d d
|
||||
* @param x x
|
||||
* @param s s
|
||||
* @param t t
|
||||
* @returns Result
|
||||
*/
|
||||
function md5ff(a, b, c, d, x, s, t) {
|
||||
return md5cmn((b & c) | (~b & d), a, b, x, s, t);
|
||||
}
|
||||
/**
|
||||
* Basic operation the algorithm uses.
|
||||
*
|
||||
* @param a a
|
||||
* @param b b
|
||||
* @param c c
|
||||
* @param d d
|
||||
* @param x x
|
||||
* @param s s
|
||||
* @param t t
|
||||
* @returns Result
|
||||
*/
|
||||
function md5gg(a, b, c, d, x, s, t) {
|
||||
return md5cmn((b & d) | (c & ~d), a, b, x, s, t);
|
||||
}
|
||||
/**
|
||||
* Basic operation the algorithm uses.
|
||||
*
|
||||
* @param a a
|
||||
* @param b b
|
||||
* @param c c
|
||||
* @param d d
|
||||
* @param x x
|
||||
* @param s s
|
||||
* @param t t
|
||||
* @returns Result
|
||||
*/
|
||||
function md5hh(a, b, c, d, x, s, t) {
|
||||
return md5cmn(b ^ c ^ d, a, b, x, s, t);
|
||||
}
|
||||
/**
|
||||
* Basic operation the algorithm uses.
|
||||
*
|
||||
* @param a a
|
||||
* @param b b
|
||||
* @param c c
|
||||
* @param d d
|
||||
* @param x x
|
||||
* @param s s
|
||||
* @param t t
|
||||
* @returns Result
|
||||
*/
|
||||
function md5ii(a, b, c, d, x, s, t) {
|
||||
return md5cmn(c ^ (b | ~d), a, b, x, s, t);
|
||||
}
|
||||
/**
|
||||
* Calculate the MD5 of an array of little-endian words, and a bit length.
|
||||
*
|
||||
* @param x Array of little-endian words
|
||||
* @param len Bit length
|
||||
* @returns MD5 Array
|
||||
*/
|
||||
function binlMD5(x, len) {
|
||||
/* append padding */
|
||||
x[len >> 5] |= 0x80 << len % 32;
|
||||
x[(((len + 64) >>> 9) << 4) + 14] = len;
|
||||
let a = 1732584193;
|
||||
let b = -271733879;
|
||||
let c = -1732584194;
|
||||
let d = 271733878;
|
||||
for (let i = 0; i < x.length; i += 16) {
|
||||
const olda = a;
|
||||
const oldb = b;
|
||||
const oldc = c;
|
||||
const oldd = d;
|
||||
a = md5ff(a, b, c, d, x[i], 7, -680876936);
|
||||
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
|
||||
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
|
||||
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
|
||||
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
|
||||
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
|
||||
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
|
||||
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
|
||||
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
|
||||
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
|
||||
c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
|
||||
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
|
||||
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
|
||||
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
|
||||
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
|
||||
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
|
||||
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
|
||||
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
|
||||
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
|
||||
b = md5gg(b, c, d, a, x[i], 20, -373897302);
|
||||
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
|
||||
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
|
||||
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
|
||||
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
|
||||
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
|
||||
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
|
||||
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
|
||||
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
|
||||
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
|
||||
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
|
||||
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
|
||||
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
|
||||
a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
|
||||
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
|
||||
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
|
||||
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
|
||||
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
|
||||
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
|
||||
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
|
||||
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
|
||||
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
|
||||
d = md5hh(d, a, b, c, x[i], 11, -358537222);
|
||||
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
|
||||
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
|
||||
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
|
||||
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
|
||||
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
|
||||
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
|
||||
a = md5ii(a, b, c, d, x[i], 6, -198630844);
|
||||
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
|
||||
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
|
||||
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
|
||||
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
|
||||
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
|
||||
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
|
||||
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
|
||||
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
|
||||
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
|
||||
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
|
||||
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
|
||||
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
|
||||
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
|
||||
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
|
||||
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
|
||||
a = safeAdd(a, olda);
|
||||
b = safeAdd(b, oldb);
|
||||
c = safeAdd(c, oldc);
|
||||
d = safeAdd(d, oldd);
|
||||
}
|
||||
return [a, b, c, d];
|
||||
}
|
||||
/**
|
||||
* Convert an array of little-endian words to a string
|
||||
*
|
||||
* @param input MD5 Array
|
||||
* @returns MD5 string
|
||||
*/
|
||||
function binl2rstr(input) {
|
||||
let output = "";
|
||||
const length32 = input.length * 32;
|
||||
for (let i = 0; i < length32; i += 8) {
|
||||
output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
/**
|
||||
* Convert a raw string to an array of little-endian words
|
||||
* Characters >255 have their high-byte silently ignored.
|
||||
*
|
||||
* @param input Raw input string
|
||||
* @returns Array of little-endian words
|
||||
*/
|
||||
function rstr2binl(input) {
|
||||
const output = [];
|
||||
const outputLen = input.length >> 2;
|
||||
for (let i = 0; i < outputLen; i += 1) {
|
||||
output[i] = 0;
|
||||
}
|
||||
const length8 = input.length * 8;
|
||||
for (let i = 0; i < length8; i += 8) {
|
||||
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
/**
|
||||
* Calculate the MD5 of a raw string
|
||||
*
|
||||
* @param s Input string
|
||||
* @returns Raw MD5 string
|
||||
*/
|
||||
function rstrMD5(s) {
|
||||
return binl2rstr(binlMD5(rstr2binl(s), s.length * 8));
|
||||
}
|
||||
/**
|
||||
* Calculates the HMAC-MD5 of a key and some data (raw strings)
|
||||
*
|
||||
* @param key HMAC key
|
||||
* @param data Raw input string
|
||||
* @returns Raw MD5 string
|
||||
*/
|
||||
function rstrHMACMD5(key, data) {
|
||||
let bkey = rstr2binl(key);
|
||||
const ipad = [];
|
||||
const opad = [];
|
||||
if (bkey.length > 16) {
|
||||
bkey = binlMD5(bkey, key.length * 8);
|
||||
}
|
||||
for (let i = 0; i < 16; i += 1) {
|
||||
ipad[i] = bkey[i] ^ 0x36363636;
|
||||
opad[i] = bkey[i] ^ 0x5c5c5c5c;
|
||||
}
|
||||
const hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
|
||||
return binl2rstr(binlMD5(opad.concat(hash), 512 + 128));
|
||||
}
|
||||
/**
|
||||
* Convert a raw string to a hex string
|
||||
*
|
||||
* @param input Raw input string
|
||||
* @returns Hex encoded string
|
||||
*/
|
||||
function rstr2hex(input) {
|
||||
const hexTab = "0123456789abcdef";
|
||||
let output = "";
|
||||
for (let i = 0; i < input.length; i += 1) {
|
||||
const x = input.charCodeAt(i);
|
||||
output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
/**
|
||||
* Encode a string as UTF-8
|
||||
*
|
||||
* @param input Input string
|
||||
* @returns UTF8 string
|
||||
*/
|
||||
function str2rstrUTF8(input) {
|
||||
return unescape(encodeURIComponent(input));
|
||||
}
|
||||
/**
|
||||
* Encodes input string as raw MD5 string
|
||||
*
|
||||
* @param s Input string
|
||||
* @returns Raw MD5 string
|
||||
*/
|
||||
function rawMD5(s) {
|
||||
return rstrMD5(str2rstrUTF8(s));
|
||||
}
|
||||
/**
|
||||
* Encodes input string as Hex encoded string
|
||||
*
|
||||
* @param s Input string
|
||||
* @returns Hex encoded string
|
||||
*/
|
||||
function hexMD5(s) {
|
||||
return rstr2hex(rawMD5(s));
|
||||
}
|
||||
/**
|
||||
* Calculates the raw HMAC-MD5 for the given key and data
|
||||
*
|
||||
* @param k HMAC key
|
||||
* @param d Input string
|
||||
* @returns Raw MD5 string
|
||||
*/
|
||||
function rawHMACMD5(k, d) {
|
||||
return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d));
|
||||
}
|
||||
/**
|
||||
* Calculates the Hex encoded HMAC-MD5 for the given key and data
|
||||
*
|
||||
* @param k HMAC key
|
||||
* @param d Input string
|
||||
* @returns Raw MD5 string
|
||||
*/
|
||||
function hexHMACMD5(k, d) {
|
||||
return rstr2hex(rawHMACMD5(k, d));
|
||||
}
|
||||
/**
|
||||
* Calculates MD5 value for a given string.
|
||||
* If a key is provided, calculates the HMAC-MD5 value.
|
||||
* Returns a Hex encoded string unless the raw argument is given.
|
||||
*
|
||||
* @param string Input string
|
||||
* @param key HMAC key
|
||||
* @param raw Raw output switch
|
||||
* @returns MD5 output
|
||||
*/
|
||||
function md5(string, key, raw) {
|
||||
if (!key) {
|
||||
if (!raw) {
|
||||
return hexMD5(string);
|
||||
}
|
||||
return rawMD5(string);
|
||||
}
|
||||
if (!raw) {
|
||||
return hexHMACMD5(key, string);
|
||||
}
|
||||
return rawHMACMD5(key, string);
|
||||
}
|
||||
|
||||
const MAX_HEADER_VALUE = 3072;
|
||||
/**
|
||||
*
|
||||
* Splits headers according to spec
|
||||
* @param headers
|
||||
* @returns Split headers
|
||||
*/
|
||||
function splitHeaders(headers) {
|
||||
const output = new Headers(headers);
|
||||
if (headers.has("x-bare-headers")) {
|
||||
const value = headers.get("x-bare-headers");
|
||||
if (value.length > MAX_HEADER_VALUE) {
|
||||
output.delete("x-bare-headers");
|
||||
let split = 0;
|
||||
for (let i = 0; i < value.length; i += MAX_HEADER_VALUE) {
|
||||
const part = value.slice(i, i + MAX_HEADER_VALUE);
|
||||
const id = split++;
|
||||
output.set(`x-bare-headers-${id}`, `;${part}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
/**
|
||||
* Joins headers according to spec
|
||||
* @param headers
|
||||
* @returns Joined headers
|
||||
*/
|
||||
function joinHeaders(headers) {
|
||||
const output = new Headers(headers);
|
||||
const prefix = "x-bare-headers";
|
||||
if (headers.has(`${prefix}-0`)) {
|
||||
const join = [];
|
||||
for (const [header, value] of headers) {
|
||||
if (!header.startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
if (!value.startsWith(";")) {
|
||||
throw new BareError(400, {
|
||||
code: "INVALID_BARE_HEADER",
|
||||
id: `request.headers.${header}`,
|
||||
message: `Value didn't begin with semi-colon.`,
|
||||
});
|
||||
}
|
||||
const id = parseInt(header.slice(prefix.length + 1));
|
||||
join[id] = value.slice(1);
|
||||
output.delete(header);
|
||||
}
|
||||
output.set(prefix, join.join(""));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
class ClientV3 extends Client {
|
||||
ws;
|
||||
http;
|
||||
meta() {
|
||||
return {};
|
||||
}
|
||||
constructor(server) {
|
||||
super(3, server);
|
||||
this.ws = new URL(this.base);
|
||||
this.http = new URL(this.base);
|
||||
if (this.ws.protocol === "https:") {
|
||||
this.ws.protocol = "wss:";
|
||||
} else {
|
||||
this.ws.protocol = "ws:";
|
||||
}
|
||||
}
|
||||
ready = true;
|
||||
async init() {
|
||||
this.ready = true;
|
||||
}
|
||||
connect(url, origin, protocols, requestHeaders, onopen, onmessage, onclose, onerror) {
|
||||
const ws = new WebSocket(this.ws);
|
||||
const cleanup = () => {
|
||||
ws.removeEventListener("close", closeListener);
|
||||
ws.removeEventListener("message", messageListener);
|
||||
};
|
||||
const closeListener = () => {
|
||||
cleanup();
|
||||
};
|
||||
const messageListener = (event) => {
|
||||
cleanup();
|
||||
// ws.binaryType is irrelevant when sending text
|
||||
if (typeof event.data !== "string")
|
||||
throw new TypeError("the first websocket message was not a text frame");
|
||||
const message = JSON.parse(event.data);
|
||||
// finally
|
||||
if (message.type !== "open") throw new TypeError("message was not of open type");
|
||||
// onMeta({
|
||||
// protocol: message.protocol,
|
||||
// setCookies: message.setCookies,
|
||||
// });
|
||||
onopen(message.protocol);
|
||||
// TODO
|
||||
ws.addEventListener("message", (ev) => {
|
||||
onmessage(ev.data);
|
||||
});
|
||||
};
|
||||
ws.addEventListener("close", closeListener);
|
||||
ws.addEventListener("message", messageListener);
|
||||
// CONNECTED TO THE BARE SERVER, NOT THE REMOTE
|
||||
ws.addEventListener(
|
||||
"open",
|
||||
(event) => {
|
||||
// getRequestHeaders().then((headers:any) =>
|
||||
WebSocketFields.prototype.send.call(
|
||||
ws,
|
||||
JSON.stringify({
|
||||
type: "connect",
|
||||
remote: url.toString(),
|
||||
protocols,
|
||||
headers: requestHeaders,
|
||||
forwardHeaders: [],
|
||||
})
|
||||
);
|
||||
// );
|
||||
},
|
||||
// only block the open event once
|
||||
{ once: true }
|
||||
);
|
||||
return ws.send.bind(ws);
|
||||
}
|
||||
async request(remote, method, body, headers, signal) {
|
||||
const options = {
|
||||
credentials: "omit",
|
||||
method: method,
|
||||
signal,
|
||||
};
|
||||
if (body !== undefined) {
|
||||
options.body = body;
|
||||
}
|
||||
options.headers = this.createBareHeaders(remote, headers);
|
||||
const response = await fetch(this.http + "?cache=" + md5(remote.toString()), options);
|
||||
const readResponse = await this.readBareResponse(response);
|
||||
// const result: Response & Partial<BareResponse> = new Response(
|
||||
// statusEmpty.includes(readResponse.status!) ? undefined : response.body,
|
||||
// {
|
||||
// status: readResponse.status,
|
||||
// statusText: readResponse.statusText ?? undefined,
|
||||
// headers: new Headers(readResponse.headers as HeadersInit),
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// result.rawHeaders = readResponse.headers;
|
||||
// result.rawResponse = response;
|
||||
return {
|
||||
body: response.body,
|
||||
headers: readResponse.headers,
|
||||
status: readResponse.status,
|
||||
statusText: readResponse.statusText,
|
||||
};
|
||||
}
|
||||
async readBareResponse(response) {
|
||||
if (!response.ok) {
|
||||
throw new BareError(response.status, await response.json());
|
||||
}
|
||||
const responseHeaders = joinHeaders(response.headers);
|
||||
const result = {};
|
||||
const xBareStatus = responseHeaders.get("x-bare-status");
|
||||
if (xBareStatus !== null) result.status = parseInt(xBareStatus);
|
||||
const xBareStatusText = responseHeaders.get("x-bare-status-text");
|
||||
if (xBareStatusText !== null) result.statusText = xBareStatusText;
|
||||
const xBareHeaders = responseHeaders.get("x-bare-headers");
|
||||
if (xBareHeaders !== null) result.headers = JSON.parse(xBareHeaders);
|
||||
return result;
|
||||
}
|
||||
createBareHeaders(remote, bareHeaders, forwardHeaders = [], passHeaders = [], passStatus = []) {
|
||||
const headers = new Headers();
|
||||
headers.set("x-bare-url", remote.toString());
|
||||
headers.set("x-bare-headers", JSON.stringify(bareHeaders));
|
||||
for (const header of forwardHeaders) {
|
||||
headers.append("x-bare-forward-headers", header);
|
||||
}
|
||||
for (const header of passHeaders) {
|
||||
headers.append("x-bare-pass-headers", header);
|
||||
}
|
||||
for (const status of passStatus) {
|
||||
headers.append("x-bare-pass-status", status.toString());
|
||||
}
|
||||
splitHeaders(headers);
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
|
||||
exports.BareClient = ClientV3;
|
||||
});
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 8793db1e2fe64cae8936c00bcd4f333f158f6ebd
|
||||
Subproject commit 2beae2ca136865d7a78ce42fea0f590c24371a1f
|
||||
4
public/img/games/fullscreen.svg
Normal file
4
public/img/games/fullscreen.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg stroke="#fff" fill="#fff" stroke-width="0" viewBox="0 0 256 256" height="1em" width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M220,48V88a12,12,0,0,1-24,0V60H168a12,12,0,0,1,0-24h40A12,12,0,0,1,220,48ZM88,196H60V168a12,12,0,0,0-24,0v40a12,12,0,0,0,12,12H88a12,12,0,0,0,0-24Zm120-40a12,12,0,0,0-12,12v28H168a12,12,0,0,0,0,24h40a12,12,0,0,0,12-12V168A12,12,0,0,0,208,156ZM88,36H48A12,12,0,0,0,36,48V88a12,12,0,0,0,24,0V60H88a12,12,0,0,0,0-24Z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 477 B |
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"nav": {
|
||||
"brand": "Alu",
|
||||
"games": "Games",
|
||||
"settings": "Settings"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"brand": "アルー",
|
||||
"games": "ゲーム",
|
||||
"settings": "設定"
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
|
|
@ -1 +0,0 @@
|
|||
console.log("Hello World!");
|
||||
166
public/marketplace/adblock/index.js
Normal file
166
public/marketplace/adblock/index.js
Normal file
File diff suppressed because one or more lines are too long
9
public/marketplace/oled-theme/theme.css
Normal file
9
public/marketplace/oled-theme/theme.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[data-theme="oled"] {
|
||||
--background-color: #000;
|
||||
--background-highlight: #111;
|
||||
--accent-color: rgb(30 0 79);
|
||||
--accent-color-brighter: rgb(60 0 155);
|
||||
--text-color: #fff;
|
||||
--text-color-accent: #dddddd;
|
||||
--dropdown-background-color: #1a1a1a;
|
||||
}
|
||||
BIN
public/marketplace/oled-theme/theme.png
Normal file
BIN
public/marketplace/oled-theme/theme.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
48
public/sw.js
48
public/sw.js
|
|
@ -1,19 +1,51 @@
|
|||
importScripts("/libcurl/index.js");
|
||||
importScripts("/epoxy/index.js");
|
||||
importScripts("/bare_transport.js");
|
||||
importScripts("/uv/uv.bundle.js");
|
||||
importScripts("/uv.config.js");
|
||||
importScripts(__uv$config.sw);
|
||||
importScripts("/uv/uv.bundle.js", "/uv.config.js", "/workerware/workerware.js");
|
||||
importScripts( __uv$config.sw);
|
||||
|
||||
const ww = new WorkerWare({
|
||||
debug: true,
|
||||
});
|
||||
|
||||
function loadExtensionScripts() {
|
||||
try {
|
||||
let db = indexedDB.open("AluDB", 1);
|
||||
db.onsuccess = () => {
|
||||
let transaction = db.result.transaction("InstalledExtensions", "readonly");
|
||||
let store = transaction.objectStore("InstalledExtensions");
|
||||
let request = store.getAll();
|
||||
request.onsuccess = () => {
|
||||
let extensions = request.result;
|
||||
extensions.forEach((extension) => {
|
||||
if (extension.type != "serviceWorker") return;
|
||||
// Loads the function to be added as a middleware into global scope.
|
||||
// The function defined should NOT immediately execute any function.
|
||||
eval(atob(extension.scriptCopy));
|
||||
ww.use({
|
||||
function: self[extension.entryNamespace][extension.entryFunc],
|
||||
name: extension.title,
|
||||
events: ["fetch"],
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(`Failed load extension scripts: ${err}`);
|
||||
}
|
||||
}
|
||||
loadExtensionScripts();
|
||||
|
||||
const uv = new UVServiceWorker();
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
self.addEventListener("fetch", async (event) => {
|
||||
event.respondWith(
|
||||
(async () => {
|
||||
let mwResponse = await ww.run(event)();
|
||||
if (mwResponse.includes(null)) {
|
||||
return;
|
||||
}
|
||||
if (event.request.url.startsWith(location.origin + __uv$config.prefix)) {
|
||||
return await uv.fetch(event);
|
||||
}
|
||||
return await fetch(event.request);
|
||||
})()
|
||||
);
|
||||
});
|
||||
});
|
||||
4924
public/unity/UnityLoader.js
Normal file
4924
public/unity/UnityLoader.js
Normal file
File diff suppressed because one or more lines are too long
423
public/unity/index.css
Normal file
423
public/unity/index.css
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
*,:before,:after {
|
||||
box-sizing: border-box;
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
border-color: #e5e7eb
|
||||
}
|
||||
|
||||
:before,:after {
|
||||
--tw-content: ""
|
||||
}
|
||||
|
||||
html {
|
||||
line-height: 1.5;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
font-family: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";
|
||||
font-feature-settings: normal;
|
||||
font-variation-settings: normal
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
line-height: inherit
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 0;
|
||||
color: inherit;
|
||||
border-top-width: 1px
|
||||
}
|
||||
|
||||
abbr:where([title]) {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: inherit
|
||||
}
|
||||
|
||||
b,strong {
|
||||
font-weight: bolder
|
||||
}
|
||||
|
||||
code,kbd,samp,pre {
|
||||
font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
|
||||
font-size: 1em
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%
|
||||
}
|
||||
|
||||
sub,sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -.25em
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -.5em
|
||||
}
|
||||
|
||||
table {
|
||||
text-indent: 0;
|
||||
border-color: inherit;
|
||||
border-collapse: collapse
|
||||
}
|
||||
|
||||
button,input,optgroup,select,textarea {
|
||||
font-family: inherit;
|
||||
font-feature-settings: inherit;
|
||||
font-variation-settings: inherit;
|
||||
font-size: 100%;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
margin: 0;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
button,select {
|
||||
text-transform: none
|
||||
}
|
||||
|
||||
button,[type=button],[type=reset],[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
background-color: transparent;
|
||||
background-image: none
|
||||
}
|
||||
|
||||
:-moz-focusring {
|
||||
outline: auto
|
||||
}
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button,::-webkit-outer-spin-button {
|
||||
height: auto
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px
|
||||
}
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
font: inherit
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item
|
||||
}
|
||||
|
||||
blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 0
|
||||
}
|
||||
|
||||
ol,ul,menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
dialog {
|
||||
padding: 0
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical
|
||||
}
|
||||
|
||||
input::-moz-placeholder,textarea::-moz-placeholder {
|
||||
opacity: 1;
|
||||
color: #9ca3af
|
||||
}
|
||||
|
||||
input::placeholder,textarea::placeholder {
|
||||
opacity: 1;
|
||||
color: #9ca3af
|
||||
}
|
||||
|
||||
button,[role=button] {
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
:disabled {
|
||||
cursor: default
|
||||
}
|
||||
|
||||
img,svg,video,canvas,audio,iframe,embed,object {
|
||||
display: block;
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
img,video {
|
||||
max-width: 100%;
|
||||
height: auto
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none
|
||||
}
|
||||
|
||||
*,:before,:after {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-pan-x: ;
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-gradient-from-position: ;
|
||||
--tw-gradient-via-position: ;
|
||||
--tw-gradient-to-position: ;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
--tw-numeric-spacing: ;
|
||||
--tw-numeric-fraction: ;
|
||||
--tw-ring-inset: ;
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgb(59 130 246 / .5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
--tw-blur: ;
|
||||
--tw-brightness: ;
|
||||
--tw-contrast: ;
|
||||
--tw-grayscale: ;
|
||||
--tw-hue-rotate: ;
|
||||
--tw-invert: ;
|
||||
--tw-saturate: ;
|
||||
--tw-sepia: ;
|
||||
--tw-drop-shadow: ;
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia:
|
||||
}
|
||||
|
||||
::backdrop {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-pan-x: ;
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-gradient-from-position: ;
|
||||
--tw-gradient-via-position: ;
|
||||
--tw-gradient-to-position: ;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
--tw-numeric-spacing: ;
|
||||
--tw-numeric-fraction: ;
|
||||
--tw-ring-inset: ;
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgb(59 130 246 / .5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
--tw-blur: ;
|
||||
--tw-brightness: ;
|
||||
--tw-contrast: ;
|
||||
--tw-grayscale: ;
|
||||
--tw-hue-rotate: ;
|
||||
--tw-invert: ;
|
||||
--tw-saturate: ;
|
||||
--tw-sepia: ;
|
||||
--tw-drop-shadow: ;
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia:
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width:640px
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width:768px
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width:1024px
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width:1280px
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width:1536px
|
||||
}
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none
|
||||
}
|
||||
|
||||
.h-5 {
|
||||
height: 1.25rem
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%
|
||||
}
|
||||
|
||||
.w-10 {
|
||||
width: 2.5rem
|
||||
}
|
||||
|
||||
.w-5 {
|
||||
width: 1.25rem
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.gap-5 {
|
||||
gap: 1.25rem
|
||||
}
|
||||
|
||||
.border {
|
||||
border-width: 1px
|
||||
}
|
||||
|
||||
.bg-slate-800 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(30 41 59 / var(--tw-bg-opacity))
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity))
|
||||
}
|
||||
|
||||
.text-slate-200 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(226 232 240 / var(--tw-text-opacity))
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration-line: underline
|
||||
}
|
||||
|
||||
.opacity-25 {
|
||||
opacity: .25
|
||||
}
|
||||
|
||||
.opacity-75 {
|
||||
opacity: .75
|
||||
}
|
||||
|
||||
#gameContainer {
|
||||
overflow: hidden
|
||||
}
|
||||
35
public/unity/instantiateUnity.js
Normal file
35
public/unity/instantiateUnity.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
const a = window.location.pathname.split("/").pop();
|
||||
|
||||
if (a && UnityLoader) {
|
||||
let t = function () {
|
||||
setTimeout(() => {
|
||||
n.Module.canvas.style.height = "100%"
|
||||
}, 1000)
|
||||
requestAnimationFrame(t)
|
||||
};
|
||||
// UnityLoader.Error.handler = e => {
|
||||
// throw document.querySelector("#loader").classList.add("hidden"),
|
||||
// document.querySelector("#error").classList.remove("hidden"),
|
||||
// e
|
||||
// };
|
||||
const n = UnityLoader.instantiate("gameContainer", `/games/${a}/data.json`, {
|
||||
onProgress: (e, r) => { },
|
||||
Module: {
|
||||
onRuntimeInitialized: () => {
|
||||
document.querySelector("#loader").classList.add("hidden"),
|
||||
document.querySelector("#gameContainer").classList.remove("hidden")
|
||||
}
|
||||
,
|
||||
wasmRequest: function (e, r) {
|
||||
e(this.wasmBinary).then(function (o) {
|
||||
r(o.instance)
|
||||
})
|
||||
},
|
||||
print: () => { },
|
||||
printErr: () => { }
|
||||
}
|
||||
});
|
||||
requestAnimationFrame(t)
|
||||
} else
|
||||
document.querySelector("#loader").classList.add("hidden"),
|
||||
document.querySelector("#error").classList.remove("hidden");
|
||||
32
public/unity/preload.js
Normal file
32
public/unity/preload.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
(function() {
|
||||
const t = document.createElement("link").relList;
|
||||
if (t && t.supports && t.supports("modulepreload"))
|
||||
return;
|
||||
for (const e of document.querySelectorAll('link[rel="modulepreload"]'))
|
||||
i(e);
|
||||
new MutationObserver(e=>{
|
||||
for (const r of e)
|
||||
if (r.type === "childList")
|
||||
for (const o of r.addedNodes)
|
||||
o.tagName === "LINK" && o.rel === "modulepreload" && i(o)
|
||||
}
|
||||
).observe(document, {
|
||||
childList: !0,
|
||||
subtree: !0
|
||||
});
|
||||
function s(e) {
|
||||
const r = {};
|
||||
return e.integrity && (r.integrity = e.integrity),
|
||||
e.referrerPolicy && (r.referrerPolicy = e.referrerPolicy),
|
||||
e.crossOrigin === "use-credentials" ? r.credentials = "include" : e.crossOrigin === "anonymous" ? r.credentials = "omit" : r.credentials = "same-origin",
|
||||
r
|
||||
}
|
||||
function i(e) {
|
||||
if (e.ep)
|
||||
return;
|
||||
e.ep = !0;
|
||||
const r = s(e);
|
||||
fetch(e.href, r)
|
||||
}
|
||||
}
|
||||
)();
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
/*global Ultraviolet*/
|
||||
self.__uv$config = {
|
||||
prefix: "/service/",
|
||||
bare: "/bare/",
|
||||
encodeUrl: Ultraviolet.codec.xor.encode,
|
||||
decodeUrl: Ultraviolet.codec.xor.decode,
|
||||
handler: "/uv/uv.handler.js",
|
||||
|
|
|
|||
6
public/workerware/WWError.js
Normal file
6
public/workerware/WWError.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
class WWError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "[WorkerWare Exception]";
|
||||
}
|
||||
}
|
||||
143
public/workerware/workerware.js
Normal file
143
public/workerware/workerware.js
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
importScripts("/workerware/WWError.js");
|
||||
const dbg = console.log.bind(console, "[WorkerWare]");
|
||||
const time = console.time.bind(console, "[WorkerWare]");
|
||||
const timeEnd = console.timeEnd.bind(console, "[WorkerWare]");
|
||||
|
||||
/*
|
||||
OPTS:
|
||||
debug - Enables debug logging.
|
||||
randomNames - Generate random names for middlewares.
|
||||
timing - Logs timing for each middleware.
|
||||
*/
|
||||
|
||||
const defaultOpt = {
|
||||
debug: false,
|
||||
randomNames: false,
|
||||
timing: false,
|
||||
};
|
||||
|
||||
const validEvents = [
|
||||
"abortpayment",
|
||||
"activate",
|
||||
"backgroundfetchabort",
|
||||
"backgroundfetchclick",
|
||||
"backgroundfetchfail",
|
||||
"backgroundfetchsuccess",
|
||||
"canmakepayment",
|
||||
"contentdelete",
|
||||
"cookiechange",
|
||||
"fetch",
|
||||
"install",
|
||||
"message",
|
||||
"messageerror",
|
||||
"notificationclick",
|
||||
"notificationclose",
|
||||
"paymentrequest",
|
||||
"periodicsync",
|
||||
"push",
|
||||
"pushsubscriptionchange",
|
||||
"sync",
|
||||
];
|
||||
|
||||
class WorkerWare {
|
||||
constructor(opt) {
|
||||
this._opt = Object.assign({}, defaultOpt, opt);
|
||||
this._middlewares = [];
|
||||
}
|
||||
info() {
|
||||
return {
|
||||
version: "0.1.0",
|
||||
middlewares: this._middlewares,
|
||||
options: this._opt,
|
||||
};
|
||||
}
|
||||
use(middleware) {
|
||||
let validateMW = this.validateMiddleware(middleware);
|
||||
if (validateMW.error) throw new WWError(validateMW.error);
|
||||
// This means the middleware is an anonymous function, or the user is silly and named their function "function"
|
||||
if (middleware.function.name == "function") middleware.name = crypto.randomUUID();
|
||||
if (!middleware.name) middleware.name = middleware.function.name;
|
||||
if (this._opt.randomNames) middleware.name = crypto.randomUUID();
|
||||
if (this._opt.debug) dbg("Adding middleware:", middleware.name);
|
||||
this._middlewares.push(middleware);
|
||||
}
|
||||
// Run all middlewares for the event type passed in.
|
||||
run(event) {
|
||||
const middlewares = this._middlewares;
|
||||
const returnList = [];
|
||||
let fn = async () => {
|
||||
for (let i = 0; i < middlewares.length; i++) {
|
||||
if (middlewares[i].events.includes(event.type)) {
|
||||
if (this._opt.timing) console.time(middlewares[i].name);
|
||||
// Add the configuration to the event object.
|
||||
event.workerware = {
|
||||
config: middlewares[i].configuration || {},
|
||||
};
|
||||
let res = await middlewares[i].function(event);
|
||||
if (this._opt.timing) console.timeEnd(middlewares[i].name);
|
||||
returnList.push(res);
|
||||
}
|
||||
}
|
||||
return returnList;
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
deleteByName(middlewareID) {
|
||||
if (this._opt.debug) dbg("Deleting middleware:", middlewareID);
|
||||
this._middlewares = this._middlewares.filter((mw) => mw.name !== middlewareID);
|
||||
}
|
||||
deleteByEvent(middlewareEvent) {
|
||||
if (this._opt.debug) dbg("Deleting middleware by event:", middlewareEvent);
|
||||
this._middlewares = this._middlewares.filter((mw) => !mw.events.includes(middlewareEvent));
|
||||
}
|
||||
get() {
|
||||
return this._middlewares;
|
||||
}
|
||||
/*
|
||||
Run a single middleware by ID.
|
||||
This assumes that the user knows what they're doing, and is running the middleware on an event that it's supposed to run on.
|
||||
*/
|
||||
runMW(id, event) {
|
||||
const middlewares = this._middlewares;
|
||||
if (this._opt.debug) dbg("Running middleware:", id);
|
||||
if (middlewares.includes(id)) {
|
||||
return middlewares[id](event);
|
||||
}
|
||||
}
|
||||
// type middlewareManifest = {
|
||||
// function: Function,
|
||||
// name?: string,
|
||||
// events: string[], // Should be a union of validEvents.
|
||||
// configuration?: Object // Optional configuration for the middleware.
|
||||
// }
|
||||
validateMiddleware(middleware) {
|
||||
if (!middleware.function)
|
||||
return {
|
||||
error: "middleware.function is required",
|
||||
};
|
||||
if (typeof middleware.function !== "function")
|
||||
return {
|
||||
error: "middleware.function must be typeof function",
|
||||
};
|
||||
if (typeof middleware.configuration !== "object" && middleware.configuration !== undefined) {
|
||||
return {
|
||||
error: "middleware.configuration must be typeof object",
|
||||
};
|
||||
}
|
||||
if (!middleware.events)
|
||||
return {
|
||||
error: "middleware.events is required",
|
||||
};
|
||||
if (!Array.isArray(middleware.events))
|
||||
return {
|
||||
error: "middleware.events must be an array",
|
||||
};
|
||||
if (middleware.events.some((ev) => !validEvents.includes(ev)))
|
||||
return {
|
||||
error: "Invalid event type! Must be one of the following: " + validEvents.join(", "),
|
||||
};
|
||||
return {
|
||||
error: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +1,39 @@
|
|||
---
|
||||
const { name, image, slugName } = Astro.props;
|
||||
const { game } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="game">
|
||||
<a href=`/games/${slugName || name}`>
|
||||
<img class="game-img" src={image} alt="" />
|
||||
<div class="game" data-name={game.name}>
|
||||
<a class="game-link" href=`/game/${game.slug}`>
|
||||
<img class="game-img" src={game.image} alt="" />
|
||||
<p class="game-title">{game.name}</p>
|
||||
</a>
|
||||
<p class="game-title">{name}</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.game {
|
||||
border: 2px solid var(--text-color);
|
||||
padding: 1rem;
|
||||
padding-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
border-radius: 15px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.game-img {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.game-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.game-title {
|
||||
color: var(--text-color);
|
||||
text-align: center;
|
||||
max-width: 125px;
|
||||
font-size: 18px;
|
||||
height: 48px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { TransportMgr, initTransport, loadSelectedTransportScript } from "./ts/TransportManager";
|
||||
import { TransportMgr, initTransport } from "./ts/TransportManager";
|
||||
import "notyf/notyf.min.css";
|
||||
import { Notyf } from "notyf";
|
||||
|
||||
|
|
@ -48,9 +48,7 @@
|
|||
}
|
||||
|
||||
function getUVProxyURL(frame: HTMLIFrameElement) {
|
||||
return window.__uv$config.decodeUrl(
|
||||
frame.contentWindow!.location.href.split("/service/")[1]
|
||||
)
|
||||
return window.__uv$config.decodeUrl(frame.contentWindow!.location.href.split("/service/")[1]);
|
||||
}
|
||||
|
||||
async function loadContent() {
|
||||
|
|
@ -64,10 +62,7 @@
|
|||
else if (!(url.startsWith("https://") || url.startsWith("http://"))) url = "http://" + url;
|
||||
if (openWith) {
|
||||
let openWithParsed = JSON.parse(openWith);
|
||||
if (
|
||||
openWithParsed.value === "newTab" ||
|
||||
(currentProxy && JSON.parse(currentProxy).value === "rammerhead")
|
||||
) {
|
||||
if (openWithParsed.value === "newTab" || (currentProxy && JSON.parse(currentProxy).value === "rammerhead")) {
|
||||
window.open(await getProxyURL(), "_blank");
|
||||
return;
|
||||
}
|
||||
|
|
@ -120,15 +115,7 @@
|
|||
iframe.classList.add("proxy-frame");
|
||||
document.body.appendChild(iframe);
|
||||
setTimeout(() => {
|
||||
iframeLoad(
|
||||
iframe,
|
||||
loadingContent,
|
||||
topbar,
|
||||
closeButton,
|
||||
shareButton,
|
||||
forwardsButton,
|
||||
backwardsButton
|
||||
);
|
||||
iframeLoad();
|
||||
}, 500);
|
||||
|
||||
function setActive() {
|
||||
|
|
@ -159,15 +146,7 @@
|
|||
// }
|
||||
// }
|
||||
|
||||
function iframeLoad(
|
||||
iframe: HTMLIFrameElement,
|
||||
loadingContent: HTMLElement,
|
||||
topbar: HTMLDivElement,
|
||||
closeButton: HTMLButtonElement,
|
||||
shareButton: HTMLImageElement,
|
||||
forwardsButton: HTMLImageElement,
|
||||
backwardsButton: HTMLImageElement
|
||||
) {
|
||||
function iframeLoad() {
|
||||
loadingContent.style.opacity = "0";
|
||||
iframe.style.opacity = "1";
|
||||
topbar.style.opacity = "1";
|
||||
|
|
@ -201,17 +180,9 @@
|
|||
let currentProxy = localStorage.getItem("alu__selectedProxy");
|
||||
let proxyFrame = document.getElementById("proxy-frame") as HTMLIFrameElement;
|
||||
if (currentProxy && JSON.parse(currentProxy).value === "rammerhead") {
|
||||
navigator.clipboard.writeText(
|
||||
window.location.origin +
|
||||
"/" +
|
||||
getCookie("rammerhead-session") +
|
||||
"/" +
|
||||
input!.value.trim()
|
||||
);
|
||||
navigator.clipboard.writeText(window.location.origin + "/" + getCookie("rammerhead-session") + "/" + input!.value.trim());
|
||||
} else {
|
||||
navigator.clipboard.writeText(
|
||||
getUVProxyURL(proxyFrame)
|
||||
);
|
||||
navigator.clipboard.writeText(getUVProxyURL(proxyFrame));
|
||||
}
|
||||
new Notyf({
|
||||
duration: 2000,
|
||||
|
|
@ -227,14 +198,12 @@
|
|||
async function formEventListener(event: Event) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
await loadSelectedTransportScript();
|
||||
TransportMgr.updateTransport();
|
||||
await TransportMgr.updateTransport();
|
||||
setTimeout(() => {
|
||||
loadContent();
|
||||
}, 200);
|
||||
}
|
||||
window.loadFormContent = loadContent;
|
||||
window.loadSelectedTransport = loadSelectedTransportScript;
|
||||
|
||||
function isUrl(val = "") {
|
||||
if (/^http(s?):\/\//.test(val) || (val.includes(".") && val.substr(0, 1) !== " ")) return true;
|
||||
|
|
@ -299,14 +268,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
function updateProxiedFavicon(iframe: HTMLIFrameElement, hasErrored = false) {
|
||||
function updateProxiedFavicon(iframe: HTMLIFrameElement) {
|
||||
if (!iframe) return;
|
||||
let proxiedFavicon = document.getElementById("proxied-favicon") as HTMLImageElement;
|
||||
if (iframe) {
|
||||
if (iframe.contentDocument) {
|
||||
let favicon =
|
||||
(iframe.contentDocument.querySelector("link[rel='icon']") as HTMLLinkElement) ||
|
||||
(iframe.contentDocument.querySelector("link[rel*='icon']") as HTMLLinkElement);
|
||||
let favicon = (iframe.contentDocument.querySelector("link[rel='icon']") as HTMLLinkElement) || (iframe.contentDocument.querySelector("link[rel*='icon']") as HTMLLinkElement);
|
||||
if (favicon && favicon.href.includes("data:image")) {
|
||||
proxiedFavicon.src = favicon.href;
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -45,10 +45,7 @@ const presetCloaks = [
|
|||
{
|
||||
presetCloaks.map((cloak: any) => {
|
||||
return (
|
||||
<div
|
||||
class="cloak-item"
|
||||
data-cloak-name={cloak.cloakTitle}
|
||||
data-cloak-icon={cloak.favicon}>
|
||||
<div class="cloak-item" data-cloak-name={cloak.cloakTitle} data-cloak-icon={cloak.favicon}>
|
||||
<img class="cloak-image" src={cloak.favicon} alt={cloak.cloakTitle} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,32 +6,16 @@ const t = i18n.inferLangUseTranslations(Astro.url);
|
|||
<div class="settings-container">
|
||||
<div class="credits-container">
|
||||
<p class="credit-item">
|
||||
{t("ultraviolet")} - <a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://titaniumnetwork.org/">Titanium Network</a
|
||||
>
|
||||
{t("ultraviolet")} - <a target="_blank" rel="noreferrer noopener" href="https://titaniumnetwork.org/">Titanium Network</a>
|
||||
</p>
|
||||
<p class="credit-item">
|
||||
{t("settings.credits.japaneseTranslations")} - <a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://wearr.dev">wearr</a
|
||||
>
|
||||
{t("settings.credits.japaneseTranslations")} - <a target="_blank" rel="noreferrer noopener" href="https://wearr.dev">wearr</a>
|
||||
</p>
|
||||
<p class="credit-item">
|
||||
{t("settings.credits.mochaandmacchiatothemes")} - <a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://github.com/catppuccin/catppuccin">Catppuccin</a
|
||||
>
|
||||
{t("settings.credits.mochaandmacchiatothemes")} - <a target="_blank" rel="noreferrer noopener" href="https://github.com/catppuccin/catppuccin">Catppuccin</a>
|
||||
</p>
|
||||
<p class="credit-item">
|
||||
Rosé Pine Theme - <a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://rosepinetheme.com/">Rosé Pine</a
|
||||
>
|
||||
Rosé Pine Theme - <a target="_blank" rel="noreferrer noopener" href="https://rosepinetheme.com/">Rosé Pine</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -23,12 +23,59 @@ const languageList = [
|
|||
<Dropdown buttonNameDefault="Alu" dropdownList={themeList} id="dropdown__selected-theme" />
|
||||
</div>
|
||||
<div class="setting__language">
|
||||
<label aria-label="Language" class="setting-label">{t("settings.customization.language")}</label
|
||||
>
|
||||
<Dropdown
|
||||
buttonNameDefault="English"
|
||||
dropdownList={languageList}
|
||||
id="dropdown__selected-language"
|
||||
/>
|
||||
<label aria-label="Language" class="setting-label">{t("settings.customization.language")}</label>
|
||||
<Dropdown buttonNameDefault="English" dropdownList={languageList} id="dropdown__selected-language" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// import IDBManager, { type ExtensionMetadata } from "@components/ts/IDBManager";
|
||||
|
||||
// let idb = IDBManager.GetIDB();
|
||||
|
||||
// if (!(idb instanceof IDBDatabase)) {
|
||||
// idb = await loadIndexedDB();
|
||||
// }
|
||||
|
||||
// let store = IDBManager.GetStore("InstalledExtensions", "readonly");
|
||||
// store.getAll().onsuccess = (event) => {
|
||||
// const result = (event.target as IDBRequest).result;
|
||||
// if (result) {
|
||||
// result.forEach((extension: ExtensionMetadata) => {
|
||||
// if (extension.type === "theme" && extension.themeName) {
|
||||
// // Create a dropdown item for the theme
|
||||
// addThemeToDropdown(extension);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
// async function loadIndexedDB(): Promise<IDBDatabase> {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// const load = IDBManager.loadIDB("AluDB", 1);
|
||||
// load.onsuccess = () => {
|
||||
// const db = load.result;
|
||||
// IDBManager.SetIDB(db);
|
||||
// resolve(idb);
|
||||
// };
|
||||
// });
|
||||
// }
|
||||
|
||||
// function addThemeToDropdown(extension: ExtensionMetadata) {
|
||||
// let dropdown = document.getElementById("dropdown__selected-theme-menu");
|
||||
// if (dropdown) {
|
||||
// let themeItem = document.createElement("li");
|
||||
// themeItem.classList.add("dropdown-item");
|
||||
// themeItem.dataset.setting = extension.themeName;
|
||||
// themeItem.textContent = extension.title!;
|
||||
// dropdown.appendChild(themeItem);
|
||||
// themeItem.addEventListener("click", () => {
|
||||
// localStorage.setItem("alu__selectedTheme", JSON.stringify({ name: extension.title, value: extension.themeName }));
|
||||
// });
|
||||
// } else {
|
||||
// setTimeout(() => {
|
||||
// addThemeToDropdown(extension);
|
||||
// }, 1000);
|
||||
// }
|
||||
// }
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -25,71 +25,39 @@ const openPageWith = [
|
|||
];
|
||||
|
||||
const transportsList = [
|
||||
{ name: "Epoxy", value: "EpxMod.EpoxyClient" },
|
||||
{ name: "Libcurl", value: "CurlMod.LibcurlClient" },
|
||||
{ name: "Bare", value: "BareMod.BareClient" },
|
||||
{ name: "Epoxy", value: "/epoxy/index.mjs" },
|
||||
{ name: "Libcurl", value: "/libcurl/index.mjs" },
|
||||
{ name: "Bare", value: "/baremod/index.mjs" },
|
||||
];
|
||||
---
|
||||
|
||||
<div class="settings-container">
|
||||
<div class="setting__selected-proxy">
|
||||
<label aria-label="Selected Proxy" class="setting-label"
|
||||
>{t("settings.proxy.selectedProxy")}</label
|
||||
>
|
||||
<Dropdown
|
||||
buttonNameDefault="Ultraviolet"
|
||||
dropdownList={proxyList}
|
||||
localStorageKey="alu__selectedProxy"
|
||||
id="dropdown__selected-proxy"
|
||||
/>
|
||||
<label aria-label="Selected Proxy" class="setting-label">{t("settings.proxy.selectedProxy")}</label>
|
||||
<Dropdown buttonNameDefault="Ultraviolet" dropdownList={proxyList} localStorageKey="alu__selectedProxy" id="dropdown__selected-proxy" />
|
||||
</div>
|
||||
<div class="setting__search-engine">
|
||||
<label aria-label="Search Engine" class="setting-label"
|
||||
>{t("settings.proxy.searchEngine")}</label
|
||||
>
|
||||
<Dropdown
|
||||
buttonNameDefault="Google"
|
||||
dropdownList={searchEngineList}
|
||||
localStorageKey="alu__searchEngine"
|
||||
id="dropdown__search-engine"
|
||||
/>
|
||||
<label aria-label="Search Engine" class="setting-label">{t("settings.proxy.searchEngine")}</label>
|
||||
<Dropdown buttonNameDefault="Google" dropdownList={searchEngineList} localStorageKey="alu__searchEngine" id="dropdown__search-engine" />
|
||||
</div>
|
||||
<div class="setting__open_with">
|
||||
<label aria-label="Open Page With" class="setting-label"
|
||||
>{t("settings.proxy.openPageWith")}</label
|
||||
>
|
||||
<Dropdown
|
||||
buttonNameDefault={t("settings.proxy.openPageWith.embed")}
|
||||
dropdownList={openPageWith}
|
||||
localStorageKey="alu__selectedOpenWith"
|
||||
id="dropdown__open-with"
|
||||
/>
|
||||
<label aria-label="Open Page With" class="setting-label">{t("settings.proxy.openPageWith")}</label>
|
||||
<Dropdown buttonNameDefault={t("settings.proxy.openPageWith.embed")} dropdownList={openPageWith} localStorageKey="alu__selectedOpenWith" id="dropdown__open-with" />
|
||||
</div>
|
||||
<div class="setting__wisp_url">
|
||||
<label aria-label="Wisp URL" for="wisp-url-input" class="setting-label"
|
||||
>{t("settings.proxy.wispURL")}</label
|
||||
>
|
||||
<label aria-label="Wisp URL" for="wisp-url-input" class="setting-label">{t("settings.proxy.wispURL")}</label>
|
||||
<Input height="50px" inputName="wisp-url" />
|
||||
</div>
|
||||
<div class="setting__bare_url">
|
||||
<label aria-label="Bare Server URL" for="bare-url-input" class="setting-label"
|
||||
>{t("settings.proxy.bareURL")}</label
|
||||
>
|
||||
<label aria-label="Bare Server URL" for="bare-url-input" class="setting-label">{t("settings.proxy.bareURL")}</label>
|
||||
<Input height="50px" inputName="bare-url" />
|
||||
</div>
|
||||
<div class="setting__transport">
|
||||
<label aria-label="Wisp Transport" class="setting-label">{t("settings.proxy.transport")}</label>
|
||||
<Dropdown
|
||||
buttonNameDefault="Epoxy"
|
||||
dropdownList={transportsList}
|
||||
localStorageKey="alu__selectedTransport"
|
||||
id="dropdown__transport"
|
||||
/>
|
||||
<Dropdown buttonNameDefault="Epoxy" dropdownList={transportsList} localStorageKey="alu__selectedTransport" id="dropdown__transport" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting__searxng-url">
|
||||
<label aria-label="SearXNG URL" for="searxng-url-input" class="setting-label"
|
||||
>{t("settings.proxy.searxngURL")}</label
|
||||
>
|
||||
<label aria-label="SearXNG URL" for="searxng-url-input" class="setting-label">{t("settings.proxy.searxngURL")}</label>
|
||||
<Input height="50px" inputName="searxng-url" defaultTextContent="https://searxng.site/" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,17 +3,48 @@
|
|||
let currentTheme = localStorage.getItem("alu__selectedTheme");
|
||||
|
||||
if (currentTheme) {
|
||||
document.documentElement.setAttribute(
|
||||
"data-theme",
|
||||
JSON.parse(currentTheme).value.toLowerCase()
|
||||
);
|
||||
document.documentElement.setAttribute("data-theme", JSON.parse(currentTheme).value.toLowerCase());
|
||||
let footer = document.getElementById("footer");
|
||||
if (footer) {
|
||||
footer.dataset.theme = JSON.parse(currentTheme).value.toLowerCase();
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem("alu__selectedTheme", JSON.stringify({ name: "Alu", value: "alu" }));
|
||||
switchTheme();
|
||||
}
|
||||
}
|
||||
switchTheme();
|
||||
|
||||
document.addEventListener("astro:after-swap", switchTheme);
|
||||
|
||||
import IDBManager from "@components/ts/IDBManager";
|
||||
const idb = IDBManager.loadIDB("AluDB", 1);
|
||||
|
||||
idb.onsuccess = () => {
|
||||
const db = idb.result;
|
||||
IDBManager.SetIDB(db);
|
||||
|
||||
let store = IDBManager.GetStore("InstalledExtensions", "readonly");
|
||||
store.getAll().onsuccess = (event) => {
|
||||
const result = (event.target as IDBRequest).result;
|
||||
if (result) {
|
||||
result.forEach((extension: IExtensionMetadata) => {
|
||||
if (extension.type === "theme" && extension.themeName) {
|
||||
// Load theme CSS
|
||||
window.loadedThemeAtob = atob(extension.scriptCopy!);
|
||||
loadStyleFromAtob();
|
||||
|
||||
document.addEventListener("astro:after-swap", () => {
|
||||
loadStyleFromAtob();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
function loadStyleFromAtob() {
|
||||
const style = document.createElement("style");
|
||||
style.textContent = window.loadedThemeAtob;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,7 @@ const { buttonNameDefault, dropdownList, id, localStorageKey } = Astro.props;
|
|||
---
|
||||
|
||||
<div class="dropdown">
|
||||
<button
|
||||
data-local-storage-key={localStorageKey}
|
||||
id={id}
|
||||
class="dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown">
|
||||
<button data-local-storage-key={localStorageKey} id={id} class="dropdown-toggle" type="button" data-toggle="dropdown">
|
||||
{buttonNameDefault}
|
||||
<span class="caret"></span></button
|
||||
>
|
||||
|
|
@ -24,7 +19,7 @@ const { buttonNameDefault, dropdownList, id, localStorageKey } = Astro.props;
|
|||
}
|
||||
</ul>
|
||||
</div>
|
||||
<style>
|
||||
<style is:global>
|
||||
.dropdown {
|
||||
box-shadow: 4px 6px 15px 0px var(--background-color);
|
||||
border-bottom-left-radius: 10px;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ const t = i18n.useTranslations(lang);
|
|||
<a href={`/${lang}/`} class="header-item">{t("nav.brand")}</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a href={`/${lang}/marketplace/`} class="header-item">Marketplace</a>
|
||||
<a href={`/${lang}/games/`} class="header-item">{t("nav.games")}</a>
|
||||
<a href={`/${lang}/settings/`} class="header-item">{t("nav.settings")}</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,5 @@
|
|||
---
|
||||
const {
|
||||
inputName,
|
||||
defaultTextContent,
|
||||
height,
|
||||
placeholder,
|
||||
className,
|
||||
defaultStyles = true,
|
||||
autocomplete = "on",
|
||||
} = Astro.props;
|
||||
const { inputName, defaultTextContent, height, placeholder, className, defaultStyles = true, autocomplete = "on" } = Astro.props;
|
||||
|
||||
const styleList = className ? className.split(" ") : [];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,4 @@
|
|||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
preserveAspectRatio="none"
|
||||
id="svg"
|
||||
viewBox="0 0 1440 390"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="transition duration-300 ease-in-out delay-150">
|
||||
<svg width="100%" height="100%" preserveAspectRatio="none" id="svg" viewBox="0 0 1440 390" xmlns="http://www.w3.org/2000/svg" class="transition duration-300 ease-in-out delay-150">
|
||||
<style>
|
||||
.path-0 {
|
||||
animation: pathAnim-0 15s;
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
|
@ -1,35 +1,14 @@
|
|||
<script>
|
||||
let primaryColor = "#8c25fa";
|
||||
let secondaryColor = "#601aab";
|
||||
console.log(
|
||||
"%cWelcome to Alu",
|
||||
`color: ${primaryColor}; font-size: 2rem; font-weight: bold; text-shadow: 2px 2px 0 ${secondaryColor};`
|
||||
);
|
||||
console.log("%cWelcome to Alu", `color: ${primaryColor}; font-size: 2rem; font-weight: bold; text-shadow: 2px 2px 0 ${secondaryColor};`);
|
||||
|
||||
console.log(
|
||||
"%cSystem Information: ",
|
||||
`color: ${primaryColor}; font-size: 1rem; font-weight: bold;`
|
||||
);
|
||||
console.log(
|
||||
"%cOS: " + navigator.platform,
|
||||
`color: ${primaryColor}; font-size: 0.75rem; font-weight: bold;`
|
||||
);
|
||||
console.log(
|
||||
"%cBrowser: " + navigator.userAgent,
|
||||
`color: ${primaryColor}; font-size: 0.75rem; font-weight: bold;`
|
||||
);
|
||||
console.log(
|
||||
"%cCPU Cores: " + navigator.hardwareConcurrency,
|
||||
`color: ${primaryColor}; font-size: 0.75rem; font-weight: bold;`
|
||||
);
|
||||
console.log("%cSystem Information: ", `color: ${primaryColor}; font-size: 1rem; font-weight: bold;`);
|
||||
console.log("%cOS: " + navigator.platform, `color: ${primaryColor}; font-size: 0.75rem; font-weight: bold;`);
|
||||
console.log("%cBrowser: " + navigator.userAgent, `color: ${primaryColor}; font-size: 0.75rem; font-weight: bold;`);
|
||||
console.log("%cCPU Cores: " + navigator.hardwareConcurrency, `color: ${primaryColor}; font-size: 0.75rem; font-weight: bold;`);
|
||||
// Cmon firefox, do we really not support this?? Basic stuff here from the "indie browser".
|
||||
console.log(
|
||||
"%cMemory: " + (navigator as any).deviceMemory + "GB",
|
||||
`color: ${primaryColor}; font-size: 0.75rem; font-weight: bold;`
|
||||
);
|
||||
console.log("%cMemory: " + (navigator as any).deviceMemory + "GB", `color: ${primaryColor}; font-size: 0.75rem; font-weight: bold;`);
|
||||
|
||||
console.log(
|
||||
"%cPlease include this information in a bug report!",
|
||||
`color: ${primaryColor}; font-size: 0.75rem; font-weight: bold;`
|
||||
);
|
||||
console.log("%cPlease include this information in a bug report!", `color: ${primaryColor}; font-size: 0.75rem; font-weight: bold;`);
|
||||
</script>
|
||||
|
|
|
|||
29
src/components/ts/IDBManager.ts
Normal file
29
src/components/ts/IDBManager.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
export let CurrentIDB: IDBDatabase;
|
||||
|
||||
export function loadIDB(name: string, version: number) {
|
||||
const request = window.indexedDB.open(name, version);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
export function GetIDB() {
|
||||
return CurrentIDB;
|
||||
}
|
||||
|
||||
export function SetIDB(idb: IDBDatabase) {
|
||||
CurrentIDB = idb;
|
||||
}
|
||||
|
||||
export function GetStore(name: string, mode: IDBTransactionMode) {
|
||||
if (CurrentIDB == null) {
|
||||
throw new Error("IDB not loaded!");
|
||||
}
|
||||
return CurrentIDB.transaction(name, mode).objectStore(name);
|
||||
}
|
||||
|
||||
export default {
|
||||
loadIDB,
|
||||
SetIDB,
|
||||
GetIDB,
|
||||
GetStore,
|
||||
};
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
type Extension = {
|
||||
name: string;
|
||||
script: string;
|
||||
serviceWorkerExtension: boolean;
|
||||
};
|
||||
|
||||
export async function retrieveExtensions() {
|
||||
const extensionsArr: Array<Extension> = [];
|
||||
const db = await new Promise<IDBDatabase>((resolve, reject) => {
|
||||
const request = indexedDB.open("AluDB", 1);
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = reject;
|
||||
});
|
||||
|
||||
const transaction = (await db).transaction("InstalledExtensions", "readwrite");
|
||||
const objectStore = transaction.objectStore("InstalledExtensions");
|
||||
const extensions: Array<Extension> = await new Promise((resolve, reject) => {
|
||||
const request = objectStore.getAll();
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = reject;
|
||||
});
|
||||
|
||||
extensions.forEach(async (extension: Extension) => {
|
||||
if (extension.serviceWorkerExtension) {
|
||||
extensionsArr.push(extension);
|
||||
}
|
||||
});
|
||||
return extensionsArr;
|
||||
}
|
||||
|
||||
export async function loadExtension(ext: Extension) {
|
||||
console.log("Loading extension: ", ext.name);
|
||||
if (ext.serviceWorkerExtension) {
|
||||
// This needs to be post message'd into the service worker
|
||||
navigator.serviceWorker.controller?.postMessage({
|
||||
listenerType: "fetch",
|
||||
payload: ext.script,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,4 @@
|
|||
// @ts-ignore
|
||||
// For some reason, VSCode can't find the bare-mux package. It exists and compiling works, but vscode throws a fit.
|
||||
import { SetTransport, registerRemoteListener } from "@mercuryworkshop/bare-mux";
|
||||
// @ts-check
|
||||
import { BareMuxConnection } from "@mercuryworkshop/bare-mux";
|
||||
declare global {
|
||||
interface Window {
|
||||
__uv$config: {
|
||||
|
|
@ -11,6 +8,8 @@ declare global {
|
|||
};
|
||||
loadFormContent: Function | null;
|
||||
loadSelectedTransport: Function | null;
|
||||
loadedThemeAtob: string;
|
||||
idb: IDBDatabase;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -21,12 +20,13 @@ type transportConfig =
|
|||
}
|
||||
| string;
|
||||
|
||||
export const wispURLDefault =
|
||||
(location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/wisp/";
|
||||
export const wispURLDefault = (location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/wisp/";
|
||||
export default class TransportManager {
|
||||
private transport = "EpxMod.EpoxyClient";
|
||||
connection: BareMuxConnection;
|
||||
private transport = "/epoxy/index.mjs";
|
||||
|
||||
constructor(transport?: string) {
|
||||
this.connection = new BareMuxConnection("/baremux/worker.js");
|
||||
if (transport) {
|
||||
this.transport = transport;
|
||||
}
|
||||
|
|
@ -38,10 +38,9 @@ export default class TransportManager {
|
|||
localStorage.setItem("alu__selectedTransport", JSON.stringify({ value: this.transport }));
|
||||
}
|
||||
}
|
||||
updateTransport() {
|
||||
async updateTransport() {
|
||||
try {
|
||||
this.setTransport(JSON.parse(localStorage.getItem("alu__selectedTransport")!).value);
|
||||
console.log(this.transport);
|
||||
await this.setTransport(JSON.parse(localStorage.getItem("alu__selectedTransport")!).value);
|
||||
} catch {
|
||||
console.log("Failed to update transport! Falling back to old transport.");
|
||||
this.setTransport(this.transport);
|
||||
|
|
@ -52,81 +51,35 @@ export default class TransportManager {
|
|||
return this.transport;
|
||||
}
|
||||
|
||||
setTransport(transport: string, wispURL = wispURLDefault) {
|
||||
async setTransport(transport: string, wispURL = wispURLDefault) {
|
||||
this.transport = transport;
|
||||
let transportConfig: transportConfig = { wisp: wispURL };
|
||||
|
||||
if (this.transport == "BareMod.BareClient") {
|
||||
if (this.transport == "/baremod/index.mjs") {
|
||||
transportConfig = localStorage.getItem("alu__bareUrl") || window.location.origin + "/bare/";
|
||||
}
|
||||
|
||||
SetTransport(this.transport, transportConfig);
|
||||
await this.connection.setTransport(transport, [transportConfig]);
|
||||
}
|
||||
}
|
||||
|
||||
export const TransportMgr = new TransportManager();
|
||||
|
||||
export async function registerSW() {
|
||||
navigator.serviceWorker.ready.then(async (sw) => {
|
||||
await registerRemoteListener(sw.active!);
|
||||
});
|
||||
export async function registerAndUpdateSW() {
|
||||
return new Promise(async (resolve) => {
|
||||
await navigator.serviceWorker.register("/sw.js").then((registration) => {
|
||||
registration.update().then(() => {
|
||||
console.log("Registered SW!");
|
||||
navigator.serviceWorker
|
||||
.register("/sw.js", {
|
||||
updateViaCache: "none",
|
||||
})
|
||||
.then(async (reg) => {
|
||||
console.log("Service worker registered!");
|
||||
reg.update();
|
||||
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function initTransport() {
|
||||
await registerRemoteListener(navigator.serviceWorker.controller!);
|
||||
TransportMgr.setTransport(
|
||||
TransportMgr.getTransport(),
|
||||
localStorage.getItem("alu__wispUrl") || wispURLDefault
|
||||
);
|
||||
}
|
||||
|
||||
export async function loadSelectedTransportScript(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
let selectedTransport = localStorage.getItem("alu__selectedTransport");
|
||||
if (!selectedTransport) {
|
||||
localStorage.setItem("alu__selectedTransport", JSON.stringify({ value: "uv" }));
|
||||
return;
|
||||
}
|
||||
let transport = JSON.parse(selectedTransport).value;
|
||||
console.log(`Loading script for ${transport}`);
|
||||
let script;
|
||||
switch (transport) {
|
||||
case "EpxMod.EpoxyClient": {
|
||||
script = document.createElement("script");
|
||||
script.src = "/epoxy/index.js";
|
||||
script.defer = true;
|
||||
break;
|
||||
}
|
||||
case "CurlMod.LibcurlClient": {
|
||||
script = document.createElement("script");
|
||||
script.src = "/libcurl/index.js";
|
||||
script.defer = true;
|
||||
break;
|
||||
}
|
||||
case "BareMod.BareClient": {
|
||||
script = document.createElement("script");
|
||||
script.src = "/bare_transport.js";
|
||||
script.defer = true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
script = document.createElement("script");
|
||||
script.src = "/epoxy/index.js";
|
||||
script.defer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
document.body.appendChild(script);
|
||||
script.onload = () => {
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
await TransportMgr.setTransport(TransportMgr.getTransport(), localStorage.getItem("alu__wispUrl") || wispURLDefault);
|
||||
}
|
||||
|
|
|
|||
19
src/components/ts/loadExtensions.ts
Normal file
19
src/components/ts/loadExtensions.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
export async function retrieveExtensions(type: ExtType) {
|
||||
const extensionsArr: Array<Extension> = [];
|
||||
const db = await new Promise<IDBDatabase>((resolve, reject) => {
|
||||
const request = indexedDB.open("AluDB", 1);
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = reject;
|
||||
});
|
||||
|
||||
const transaction = db.transaction("InstalledExtensions", "readwrite");
|
||||
const objectStore = transaction.objectStore("InstalledExtensions");
|
||||
const extensions: Array<Extension> = await new Promise((resolve, reject) => {
|
||||
const request = objectStore.getAll();
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = reject;
|
||||
});
|
||||
|
||||
extensions.forEach(async (extension: Extension) => {});
|
||||
return extensionsArr;
|
||||
}
|
||||
250
src/components/ts/marketplace.ts
Normal file
250
src/components/ts/marketplace.ts
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
import "notyf/notyf.min.css";
|
||||
import { Notyf } from "notyf";
|
||||
import marketplaceManifest from "../../json/marketplace.json";
|
||||
const installButtons = document.getElementsByClassName("btn-install");
|
||||
import IDBManager from "./IDBManager";
|
||||
|
||||
const extManifest = marketplaceManifest as ExtensionMetadataJSON;
|
||||
|
||||
// This just makes it shorter to type
|
||||
interface HTMLButton extends HTMLButtonElement {}
|
||||
|
||||
enum EXT_RETURN {
|
||||
ACTION_SUCCESS = 0,
|
||||
INSTALL_FAILED = -1,
|
||||
ALREADY_INSTALLED = 1,
|
||||
}
|
||||
|
||||
Array.from(installButtons).forEach((btn) => {
|
||||
btn.addEventListener("click", async (event) => {
|
||||
const ele = event.target as HTMLButton;
|
||||
const title = ele.dataset.title;
|
||||
let notification = new Notyf({
|
||||
duration: 999999,
|
||||
position: { x: "right", y: "bottom" },
|
||||
dismissible: true,
|
||||
ripple: true,
|
||||
});
|
||||
let installNotif = notification.success(`Installing ${title}...`);
|
||||
if (ele.dataset.slug) {
|
||||
let obj = await getMarketplaceObj(ele.dataset.slug);
|
||||
installExtension(obj, ele.dataset.slug)
|
||||
.then((ret) => {
|
||||
let notifMessage: string;
|
||||
let timeout = 2000;
|
||||
switch (ret.code) {
|
||||
case EXT_RETURN.ACTION_SUCCESS:
|
||||
notifMessage = `Installed ${title} Successfully!`;
|
||||
// Unregister the service worker if it's a service worker
|
||||
if (obj.type === "serviceWorker") {
|
||||
navigator.serviceWorker.getRegistration().then((reg) => {
|
||||
if (reg) {
|
||||
reg.unregister().then(() => {
|
||||
console.log("Service worker unregistered!");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case EXT_RETURN.ALREADY_INSTALLED:
|
||||
notifMessage = `${title} is already installed!`;
|
||||
timeout = 0;
|
||||
break;
|
||||
case EXT_RETURN.INSTALL_FAILED:
|
||||
// We should NEVER get here, but just in case.
|
||||
notifMessage = `Failed to install ${title}!`;
|
||||
break;
|
||||
}
|
||||
setTimeout(() => {
|
||||
notification.dismiss(installNotif);
|
||||
notification.options.duration = 2000;
|
||||
notification.success(notifMessage);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
notification.options.duration = 999999;
|
||||
let btn = document.querySelector(`button[data-slug="${ret.slug}"]`) as HTMLButton;
|
||||
setInstallBtnText(btn);
|
||||
}, timeout);
|
||||
})
|
||||
.catch(() => {
|
||||
notification.dismiss(installNotif);
|
||||
notification.options.duration = 2000;
|
||||
notification.error(`Failed to install ${title}!`);
|
||||
notification.options.duration = 999999;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function getMarketplaceObj(slug: string): Promise<IExtensionMetadata> {
|
||||
const manifest = extManifest[slug];
|
||||
manifest.scriptCopy = btoa(await fetch(manifest.script).then((res) => res.text()));
|
||||
return manifest;
|
||||
}
|
||||
|
||||
async function installExtension(ext: IExtensionMetadata, slug: string): Promise<InstallReturn> {
|
||||
return new Promise<InstallReturn>((resolve, reject) => {
|
||||
const request = IDBManager.GetIDB();
|
||||
const transaction = request.transaction("InstalledExtensions", "readwrite");
|
||||
const store = transaction.objectStore("InstalledExtensions");
|
||||
const extensionObject = {
|
||||
slug: slug,
|
||||
...ext,
|
||||
};
|
||||
let slugCheck = store.get(slug);
|
||||
slugCheck.onsuccess = async () => {
|
||||
if (slugCheck.result != null) {
|
||||
resolve({ code: EXT_RETURN.ALREADY_INSTALLED, slug: slug });
|
||||
} else {
|
||||
const addRequest = store.add(extensionObject);
|
||||
addRequest.onerror = () => {
|
||||
console.error(`Error installing ${slug}!`);
|
||||
reject({ code: EXT_RETURN.INSTALL_FAILED, slug: slug });
|
||||
};
|
||||
addRequest.onsuccess = () => {
|
||||
resolve({ code: EXT_RETURN.ACTION_SUCCESS, slug: slug });
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function addUninstallEventListeners() {
|
||||
document.querySelectorAll("button[data-uninstall-slug]").forEach((btn) => {
|
||||
btn.addEventListener("click", async (event) => {
|
||||
if (!confirm("Are you sure you want to uninstall this extension?")) {
|
||||
return;
|
||||
}
|
||||
let uninst = await uninstallExtension((event.target as HTMLButton).dataset.uninstallSlug!);
|
||||
let notification = new Notyf({
|
||||
duration: 999999,
|
||||
position: { x: "right", y: "bottom" },
|
||||
dismissible: true,
|
||||
ripple: true,
|
||||
});
|
||||
switch (uninst.code) {
|
||||
case EXT_RETURN.ACTION_SUCCESS:
|
||||
notification.success(`Uninstalled ${uninst.title}!`);
|
||||
let btn = document.querySelector(`button[data-slug="${uninst.slug}"]`) as HTMLButton;
|
||||
btn.disabled = false;
|
||||
btn.textContent = "Install";
|
||||
btn.classList.remove("installed");
|
||||
(event.target as HTMLButton).classList.add("btn-hidden");
|
||||
break;
|
||||
case EXT_RETURN.INSTALL_FAILED:
|
||||
notification.error(`Failed to uninstall ${uninst.title}!`);
|
||||
break;
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addUninstallEventListeners();
|
||||
document.addEventListener("astro:after-swap", () => {
|
||||
addUninstallEventListeners();
|
||||
});
|
||||
|
||||
async function uninstallExtension(slug: string): Promise<InstallReturn> {
|
||||
return new Promise<InstallReturn>((resolve, reject) => {
|
||||
if (!slug || slug == null) {
|
||||
reject({ code: EXT_RETURN.INSTALL_FAILED, slug: slug });
|
||||
}
|
||||
const request = IDBManager.GetIDB();
|
||||
const transaction = request.transaction("InstalledExtensions", "readwrite");
|
||||
const store = transaction.objectStore("InstalledExtensions");
|
||||
|
||||
let ext = store.get(slug);
|
||||
ext.onsuccess = async () => {
|
||||
if (ext.result == null) {
|
||||
reject({ code: EXT_RETURN.INSTALL_FAILED, slug: slug });
|
||||
}
|
||||
if (ext.result.type === "theme") {
|
||||
let currTheme = localStorage.getItem("alu__selectedTheme");
|
||||
if (currTheme) {
|
||||
if (JSON.parse(currTheme!).value == ext.result.themeName) {
|
||||
console.log("Reverting theme to default!");
|
||||
localStorage.setItem("alu__selectedTheme", JSON.stringify({ name: "Alu", value: "alu" }));
|
||||
}
|
||||
}
|
||||
}
|
||||
const deleteRequest = store.delete(slug);
|
||||
deleteRequest.onerror = () => {
|
||||
console.error(`Error uninstalling ${slug}!`);
|
||||
reject({ code: EXT_RETURN.INSTALL_FAILED, slug: slug, title: slug });
|
||||
};
|
||||
deleteRequest.onsuccess = () => {
|
||||
navigator.serviceWorker.getRegistration().then((reg) => {
|
||||
if (reg) {
|
||||
reg.unregister().then(() => {
|
||||
console.log("Service worker unregistered!");
|
||||
});
|
||||
}
|
||||
});
|
||||
resolve({ code: EXT_RETURN.ACTION_SUCCESS, slug: slug, title: ext.result.title });
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function setInstallBtnText(btn: HTMLButton) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = "Installed";
|
||||
btn.classList.add("installed");
|
||||
}
|
||||
|
||||
function getInstallStatus() {
|
||||
let installBtns = document.querySelectorAll("button[data-slug]") as NodeListOf<HTMLButton>;
|
||||
let transaction = IDBManager.GetStore("InstalledExtensions", "readonly").transaction;
|
||||
let store = transaction.objectStore("InstalledExtensions");
|
||||
let cursor = store.openCursor();
|
||||
cursor.onsuccess = () => {
|
||||
let res = cursor.result;
|
||||
if (res) {
|
||||
let slug = res.value.slug;
|
||||
installBtns.forEach((btn) => {
|
||||
if (btn.dataset.slug == slug) {
|
||||
setInstallBtnText(btn);
|
||||
document.querySelector(`button[data-uninstall-slug="${slug}"]`)!.classList.remove("btn-hidden");
|
||||
}
|
||||
});
|
||||
res.continue();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function InitIDB() {
|
||||
if (!window.indexedDB) {
|
||||
console.error("This browser doesn't support IndexedDB");
|
||||
document.getElementById("support-warning")!.innerText = "Your browser doesn't support IndexedDB. Please use a different browser!";
|
||||
return;
|
||||
}
|
||||
if (IDBManager.GetIDB() != null) {
|
||||
getInstallStatus();
|
||||
return;
|
||||
}
|
||||
const request = IDBManager.loadIDB("AluDB", 1);
|
||||
request.onerror = (event: Event) => {
|
||||
console.error("Database error: " + (event.target as any).errorCode);
|
||||
};
|
||||
request.onsuccess = () => {
|
||||
console.log("Database opened successfully");
|
||||
IDBManager.SetIDB(request.result);
|
||||
getInstallStatus();
|
||||
};
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result;
|
||||
if (!db.objectStoreNames.contains("InstalledExtensions")) {
|
||||
db.createObjectStore("InstalledExtensions", { keyPath: "slug" });
|
||||
console.log("Database setup complete");
|
||||
}
|
||||
};
|
||||
}
|
||||
InitIDB();
|
||||
|
||||
document.addEventListener("astro:after-swap", () => {
|
||||
InitIDB();
|
||||
});
|
||||
22
src/env.d.ts
vendored
22
src/env.d.ts
vendored
|
|
@ -55,14 +55,9 @@ declare module "astro-i18n" {
|
|||
/** Typed astro-i18n config definition. */
|
||||
export function defineAstroI18nConfig(config: Partial<AstroI18nConfig>): Partial<AstroI18nConfig>;
|
||||
/** The `astro-i18n` middleware. */
|
||||
export function useAstroI18n(
|
||||
config?: Partial<AstroI18nConfig> | string,
|
||||
formatters?: TranslationFormatters
|
||||
): (...args: any[]) => any;
|
||||
export function useAstroI18n(config?: Partial<AstroI18nConfig> | string, formatters?: TranslationFormatters): (...args: any[]) => any;
|
||||
/** Workaround function to make astroI18n work inside getStaticPaths. This is because Astro's getStaticPaths runs before everything which doesn't allows astroI18n to update its state automatically. */
|
||||
function createGetStaticPaths(
|
||||
callback: (props: GetStaticPathsProps) => GetStaticPathsItem[] | Promise<GetStaticPathsItem[]>
|
||||
): (
|
||||
function createGetStaticPaths(callback: (props: GetStaticPathsProps) => GetStaticPathsItem[] | Promise<GetStaticPathsItem[]>): (
|
||||
props: GetStaticPathsProps & {
|
||||
astroI18n?: {
|
||||
locale: string;
|
||||
|
|
@ -78,9 +73,7 @@ declare module "astro-i18n" {
|
|||
key: T | (string & {}),
|
||||
...args: undefined extends TranslationVariables[T]
|
||||
? [
|
||||
properties?: keyof TranslationVariables extends T
|
||||
? Record<string, unknown>
|
||||
: TranslationVariables[T],
|
||||
properties?: keyof TranslationVariables extends T ? Record<string, unknown> : TranslationVariables[T],
|
||||
options?: {
|
||||
route?: Route | (string & {});
|
||||
locale?: Locale | (string & {});
|
||||
|
|
@ -163,9 +156,7 @@ declare module "astro-i18n" {
|
|||
key: T | (string & {}),
|
||||
...args: undefined extends TranslationVariables[T]
|
||||
? [
|
||||
properties?: keyof TranslationVariables extends T
|
||||
? Record<string, unknown>
|
||||
: TranslationVariables[T],
|
||||
properties?: keyof TranslationVariables extends T ? Record<string, unknown> : TranslationVariables[T],
|
||||
options?: {
|
||||
route?: Route | (string & {});
|
||||
locale?: Locale | (string & {});
|
||||
|
|
@ -229,10 +220,7 @@ declare module "astro-i18n" {
|
|||
/** Tries to parse one of the configured locales out of the given route. If no configured locale is found it will return `null`. */
|
||||
extractRouteLocale(route: string): string | null;
|
||||
/** Initializes astro-i18n on the server-side. */
|
||||
initialize(
|
||||
config?: Partial<AstroI18nConfig> | string,
|
||||
formatters?: TranslationFormatters = {}
|
||||
): Promise<void>;
|
||||
initialize(config?: Partial<AstroI18nConfig> | string, formatters?: TranslationFormatters = {}): Promise<void>;
|
||||
/** Redirects the user to the given destination. */
|
||||
redirect(destination: string | URL, status = 301);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"footer.aluProject": "Alu Project",
|
||||
|
||||
"games.title": "Games",
|
||||
"games.search": "Search...",
|
||||
|
||||
"settings.title": "Settings",
|
||||
"settings.proxy": "Proxy",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"footer.aluProject": "Alu Project",
|
||||
|
||||
"games.title": "ゲーム",
|
||||
"games.search": "検索...",
|
||||
|
||||
"settings.title": "設定",
|
||||
"settings.proxy": "プロキシ",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
{
|
||||
"2048": {
|
||||
"name": "2048",
|
||||
"description": "Join the numbers and get to the 2048 tile!",
|
||||
"image": "/games/2048/logo.png",
|
||||
"slug": "2048"
|
||||
},
|
||||
"adofai": {
|
||||
"name": "A Dance of Fire and Ice",
|
||||
"description": "A Dance of Fire and Ice is a strict rhythm game. Keep your focus as you guide two orbiting planets along a winding path without breaking their perfect equilibrium. Press on every beat of the music to move in a line. Every pattern has its own rhythm to it. It can get difficult. This game is purely based on rhythm, so use your ears more than your sight.",
|
||||
"image": "/games/adofai/logo.webp",
|
||||
"slug": "adofai",
|
||||
"unity": true
|
||||
},
|
||||
"backrooms": {
|
||||
"name": "The Backrooms",
|
||||
"image": "/games/backrooms/logo.png",
|
||||
|
|
@ -14,6 +22,11 @@
|
|||
"image": "/games/baldi/logo.png",
|
||||
"slug": "baldi"
|
||||
},
|
||||
"basketball-stars": {
|
||||
"name": "Basketball Stars",
|
||||
"image": "/games/basketball-stars/logo.png",
|
||||
"slug": "basketball-stars"
|
||||
},
|
||||
"cannon-basketball-4": {
|
||||
"name": "Cannon Basketball 4",
|
||||
"image": "/games/cannon-basketball-4/logo.png",
|
||||
|
|
@ -79,6 +92,27 @@
|
|||
"image": "/games/draw-the-hill/logo.png",
|
||||
"slug": "draw-the-hill"
|
||||
},
|
||||
"ducklife": {
|
||||
"name": "Duck Life",
|
||||
"description": "Duck Life is the first game in the Duck Life series. A tornado has struck your farm and destroyed everything. All that remains is a single duck egg. Train this duckling to peak athletic form so you can earn money to rebuild the farm.",
|
||||
"image": "/games/ducklife/logo.png",
|
||||
"slug": "ducklife",
|
||||
"unity": true
|
||||
},
|
||||
"ducklife3": {
|
||||
"name": "Duck Life 3",
|
||||
"description": "Duck Life is the first game in the Duck Life series. A tornado has struck your farm and destroyed everything. All that remains is a single duck egg. Train this duckling to peak athletic form so you can earn money to rebuild the farm.",
|
||||
"image": "/games/ducklife3/logo.webp",
|
||||
"slug": "ducklife3",
|
||||
"unity": true
|
||||
},
|
||||
"ducklife4": {
|
||||
"name": "Duck Life 4",
|
||||
"description": "Duck Life is the first game in the Duck Life series. A tornado has struck your farm and destroyed everything. All that remains is a single duck egg. Train this duckling to peak athletic form so you can earn money to rebuild the farm.",
|
||||
"image": "/games/ducklife/logo.png",
|
||||
"slug": "ducklife4",
|
||||
"unity": true
|
||||
},
|
||||
"evil-glitch": {
|
||||
"name": "Evil Glitch",
|
||||
"image": "/games/evil-glitch/logo.png",
|
||||
|
|
@ -124,11 +158,6 @@
|
|||
"image": "/games/game-inside/logo.png",
|
||||
"slug": "game-inside"
|
||||
},
|
||||
"google-snake": {
|
||||
"name": "Google Snake",
|
||||
"image": "/games/google-snake/logo.png",
|
||||
"slug": "google-snake"
|
||||
},
|
||||
"grindcraft": {
|
||||
"name": "Grindcraft",
|
||||
"image": "/games/grindcraft/logo.png",
|
||||
|
|
@ -184,16 +213,22 @@
|
|||
"image": "/games/moto-x3m-winter/logo.png",
|
||||
"slug": "moto-x3m-winter"
|
||||
},
|
||||
"osu": {
|
||||
"name": "osu!",
|
||||
"image": "/games/osu/logo.png",
|
||||
"slug": "osu"
|
||||
},
|
||||
"retro-bowl": {
|
||||
"name": "Retro Bowl",
|
||||
"image": "/games/retro-bowl/logo.png",
|
||||
"slug": "retro-bowl"
|
||||
},
|
||||
"rooftop-sniper": {
|
||||
"name": "Rooftop Snipers",
|
||||
"image": "/games/rooftop-sniper/logo.png",
|
||||
"slug": "rooftop-sniper",
|
||||
"unity": true
|
||||
},
|
||||
"sans-fight": {
|
||||
"name": "Sans Fight",
|
||||
"image": "/games/sans-fight/icon-256.png",
|
||||
"slug": "sans-fight"
|
||||
},
|
||||
"slope": {
|
||||
"name": "Slope",
|
||||
"image": "/games/slope/logo.png",
|
||||
|
|
@ -204,11 +239,6 @@
|
|||
"image": "/games/sm64/logo.png",
|
||||
"slug": "sm64"
|
||||
},
|
||||
"solitaire": {
|
||||
"name": "Solitaire",
|
||||
"image": "/games/solitaire/logo.png",
|
||||
"slug": "solitaire"
|
||||
},
|
||||
"suika-combination": {
|
||||
"name": "Suika Combination",
|
||||
"image": "/games/suika-combination/logo.webp",
|
||||
|
|
@ -224,6 +254,12 @@
|
|||
"image": "/games/there-is-no-game/logo.png",
|
||||
"slug": "there-is-no-game"
|
||||
},
|
||||
"tunnelrush": {
|
||||
"name": "Tunnel Rush",
|
||||
"image": "/games/tunnelrush/logo.jpg",
|
||||
"slug": "tunnelrush",
|
||||
"unity": true
|
||||
},
|
||||
"ul6": {
|
||||
"name": "Ultima 6",
|
||||
"image": "/games/ul6/logo.png",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
{
|
||||
"dev.wearr.adblock": {
|
||||
"title": "Adblocker",
|
||||
"title": "Alu Adblocker",
|
||||
"description": "Alu Adblocker is the best adblocker for web proxy services. Blocking up to 97% of all trackers and ads.",
|
||||
"version": "0.0.1",
|
||||
"image": "/marketplace/adblock/adblock.png",
|
||||
"script": "/marketplace/adblock/adblocker.js"
|
||||
"script": "/marketplace/adblock/index.js",
|
||||
"type": "serviceWorker",
|
||||
"entryNamespace": "adblockExt",
|
||||
"entryFunc": "filterRequest"
|
||||
},
|
||||
"dev.wearr.adblock2": {
|
||||
"title": "Adblocker2",
|
||||
"version": "0.0.2",
|
||||
"image": "/marketplace/adblock/adblock.png",
|
||||
"script": "/marketplace/adblock/adblocker.js"
|
||||
"dev.wearr.oled-theme": {
|
||||
"title": "OLED Theme",
|
||||
"description": "A beautiful OLED theme for Alu.",
|
||||
"version": "0.0.1",
|
||||
"image": "/marketplace/oled-theme/theme.png",
|
||||
"script": "/marketplace/oled-theme/theme.css",
|
||||
"type": "theme",
|
||||
"themeName": "oled"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,25 +27,16 @@ const { title, optionalPreloads } = Astro.props;
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="title" content="Alu" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Alu is a sleek web proxy supporting multiple standards of communication, and wide levels of customization."
|
||||
/>
|
||||
<meta name="description" content="Alu is a sleek web proxy supporting multiple standards of communication, and wide levels of customization." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://aluu.xyz" />
|
||||
<meta property="og:title" content="Alu" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Alu is a sleek web proxy supporting multiple standards of communication, and wide levels of customization."
|
||||
/>
|
||||
<meta property="og:description" content="Alu is a sleek web proxy supporting multiple standards of communication, and wide levels of customization." />
|
||||
<meta property="og:image" content="/logo.png" />
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://aluu.xyz" />
|
||||
<meta property="twitter:title" content="Alu" />
|
||||
<meta
|
||||
property="twitter:description"
|
||||
content="Alu is a sleek web proxy supporting multiple standards of communication, and wide levels of customization."
|
||||
/>
|
||||
<meta property="twitter:description" content="Alu is a sleek web proxy supporting multiple standards of communication, and wide levels of customization." />
|
||||
<meta property="twitter:image" content="/logo.png" />
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
<link href="/varela-round.css" rel="stylesheet" as="style" />
|
||||
|
|
@ -54,13 +45,15 @@ const { title, optionalPreloads } = Astro.props;
|
|||
return <link rel="preload" href={item.href} as={item.as} />;
|
||||
})
|
||||
}
|
||||
<script src="https://www.googletagmanager.com/gtag/js?id=G-P1JX4G9KSF" is:inline></script>
|
||||
<script is:inline>
|
||||
<script src="https://www.googletagmanager.com/gtag/js?id=G-P1JX4G9KSF" type="text/partytown" is:inline></script>
|
||||
<script type="text/partytown" is:inline>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-P1JX4G9KSF');
|
||||
function gtag() {
|
||||
window.dataLayer.push(arguments);
|
||||
}
|
||||
gtag("js", new Date());
|
||||
|
||||
gtag("config", "G-P1JX4G9KSF");
|
||||
</script>
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title}</title>
|
||||
|
|
@ -76,6 +69,9 @@ const { title, optionalPreloads } = Astro.props;
|
|||
<Footer />
|
||||
<style is:global>
|
||||
:root {
|
||||
--background-color: black;
|
||||
}
|
||||
[data-theme="alu"] {
|
||||
--background-color: #211d29;
|
||||
--background-highlight: #35314a;
|
||||
--accent-color: #624978;
|
||||
|
|
@ -92,6 +88,10 @@ const { title, optionalPreloads } = Astro.props;
|
|||
color: var(--text-color);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, p, span {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
[data-theme="mocha"] {
|
||||
/* Catppucin Mocha theme */
|
||||
--background-color: #1e1e2e;
|
||||
|
|
@ -170,7 +170,7 @@ const { title, optionalPreloads } = Astro.props;
|
|||
color: var(--text-color);
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
font-size: 40px;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
.title-desc {
|
||||
color: var(--text-color);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import games from "../../json/games.json";
|
|||
import GameItem from "@components/GameItem.astro";
|
||||
|
||||
import { STATIC_PATHS, i18n } from "@i18n/utils";
|
||||
import Input from "@components/UI/Input.astro";
|
||||
const t = i18n.inferLangUseTranslations(Astro.url);
|
||||
|
||||
export function getStaticPaths() {
|
||||
|
|
@ -13,41 +14,83 @@ export function getStaticPaths() {
|
|||
|
||||
<Layout title={t("pages.games")}>
|
||||
<h1 class="title-text">{t("games.title")}</h1>
|
||||
<div class="search">
|
||||
<Input inputName="games-search" placeholder={t("games.search")} height="30px" />
|
||||
</div>
|
||||
<div class="no-results"></div>
|
||||
<main id="main-content" class="grid">
|
||||
{
|
||||
Object.keys(games).map((key) => {
|
||||
const game = (games as any)[key];
|
||||
return <GameItem name={game.name} image={game.image} slugName={game.slug} />;
|
||||
return <GameItem game={game} />;
|
||||
})
|
||||
}
|
||||
</main>
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
let search = document.getElementById("games-search-input") as HTMLInputElement;
|
||||
let mainContent = document.getElementById("main-content") as HTMLDivElement;
|
||||
search.addEventListener("input", () => {
|
||||
let filter = search.value.toUpperCase();
|
||||
let games = mainContent.children;
|
||||
let results = 0;
|
||||
for (let i = 0; i < games.length; i++) {
|
||||
let game = games[i] as HTMLDivElement;
|
||||
let name = game.getAttribute("data-name")!;
|
||||
if (name.toUpperCase().indexOf(filter) > -1) {
|
||||
game.style.display = "";
|
||||
results++;
|
||||
} else {
|
||||
game.style.display = "none";
|
||||
}
|
||||
}
|
||||
console.log(results);
|
||||
if (results === 0) {
|
||||
let noResults = document.querySelector(".no-results") as HTMLDivElement;
|
||||
if (noResults) {
|
||||
noResults.style.display = "block";
|
||||
noResults.innerHTML = "No results found";
|
||||
}
|
||||
} else {
|
||||
let noResults = document.querySelector(".no-results") as HTMLDivElement;
|
||||
if (noResults) {
|
||||
noResults.style.display = "none";
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.title-text {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.search {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.grid {
|
||||
color: var(--text-color);
|
||||
margin-top: -10px;
|
||||
height: max-content;
|
||||
width: calc(100% - 60px);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(175px, 1fr));
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-left: 20px;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 973px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 467px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
.no-results {
|
||||
text-align: center;
|
||||
height: 15rem;
|
||||
width: 100%;
|
||||
font-size: 2rem;
|
||||
margin-top: 10px;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -18,15 +18,7 @@ export function getStaticPaths() {
|
|||
<section id="proxy-input">
|
||||
<div class="form-wrapper">
|
||||
<form class="url-input-form" id="url-input-form">
|
||||
<Input
|
||||
className="url-input"
|
||||
inputName="url"
|
||||
height="50px"
|
||||
placeholder={t("menu.search")}
|
||||
defaultStyles={false}
|
||||
transition:persist
|
||||
autocomplete="off"
|
||||
/>
|
||||
<Input className="url-input" inputName="url" height="50px" placeholder={t("menu.search")} defaultStyles={false} transition:persist autocomplete="off" />
|
||||
<div id="search-suggestions"></div>
|
||||
<div id="loading-content">Loading...</div>
|
||||
</form>
|
||||
|
|
@ -58,11 +50,7 @@ export function getStaticPaths() {
|
|||
<h2>{t("faq.contributeToAlu")}</h2>
|
||||
<p>
|
||||
{t("faq.contributeToAlu.answer.segment1")}
|
||||
<Link
|
||||
href="https://www.patreon.com/wearr"
|
||||
newTab
|
||||
content={t("faq.contributeToAlu.answer.patreonLinkText")}
|
||||
/>
|
||||
<Link href="https://www.patreon.com/wearr" newTab content={t("faq.contributeToAlu.answer.patreonLinkText")} />
|
||||
{t("faq.contributeToAlu.answer.segment2")}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -75,11 +63,11 @@ export function getStaticPaths() {
|
|||
</main>
|
||||
<ProxyRegistrar />
|
||||
<script>
|
||||
import { initTransport, registerSW } from "@components/ts/TransportManager";
|
||||
import { initTransport, registerAndUpdateSW } from "@components/ts/TransportManager";
|
||||
type Suggestion = {
|
||||
phrase: string;
|
||||
};
|
||||
await registerSW();
|
||||
await registerAndUpdateSW();
|
||||
|
||||
async function sendAPIRequest(urlInput: HTMLInputElement, searchSuggestions: HTMLDivElement) {
|
||||
if (!urlInput) throw new Error("urlInput is null");
|
||||
|
|
@ -160,10 +148,6 @@ export function getStaticPaths() {
|
|||
height: 56px;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 38px;
|
||||
}
|
||||
|
||||
.url-input-form {
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
|
|
|||
|
|
@ -6,196 +6,50 @@ import marketplace from "../../json/marketplace.json";
|
|||
export const getStaticPaths = () => {
|
||||
return STATIC_PATHS;
|
||||
};
|
||||
|
||||
type MarketplaceItem = {
|
||||
title: string;
|
||||
version: string | number;
|
||||
image: string;
|
||||
script: string;
|
||||
};
|
||||
---
|
||||
|
||||
<Layout title="Marketplace | Alu">
|
||||
<div id="main-content">
|
||||
<h1 class="title-text">Marketplace</h1>
|
||||
<p class="title-desc">Install custom userscripts and themes for Alu.</p>
|
||||
<p class="title-desc">(WIP) Install custom extensions for Alu!</p>
|
||||
<div id="support-warning"></div>
|
||||
<div class="marketplace-ext-grid">
|
||||
{
|
||||
Object.keys(marketplace).map((mp_item) => {
|
||||
const item = (marketplace as { [key: string]: MarketplaceItem })[mp_item];
|
||||
const item = (marketplace as { [key: string]: IExtensionMetadata })[mp_item];
|
||||
const slug = mp_item;
|
||||
return (
|
||||
<div class="marketplace-item" data-slug={slug}>
|
||||
<img class="marketplace-item-image" src={item.image} alt={`${item.title} Logo`} />
|
||||
<div class="marketplace-item-title">{item.title}</div>
|
||||
<button class="marketplace-install-btn" data-slug={slug} data-title={item.title}>
|
||||
<div class="marketplace-item-desc">{item.description}</div>
|
||||
<button class="marketplace-btn btn-install" data-slug={slug} data-title={item.title}>
|
||||
Install
|
||||
</button>
|
||||
<button class="marketplace-btn btn-hidden" data-uninstall-slug={slug}>
|
||||
Uninstall
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<script src="@components/ts/marketplace.ts"></script>
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
import "notyf/notyf.min.css";
|
||||
import { Notyf } from "notyf";
|
||||
import marketplaceManifest from "../../json/marketplace.json";
|
||||
const installButtons = document.getElementsByClassName(
|
||||
"marketplace-install-btn"
|
||||
) as HTMLCollectionOf<HTMLButtonElement>;
|
||||
Array.from(installButtons).forEach((btn) => {
|
||||
btn.addEventListener("click", async (event) => {
|
||||
const ele = event.target as HTMLButtonElement;
|
||||
const title = ele.dataset.title;
|
||||
let notification = new Notyf({
|
||||
duration: 999999,
|
||||
position: { x: "right", y: "bottom" },
|
||||
dismissible: true,
|
||||
ripple: true,
|
||||
});
|
||||
let installNotif = notification.success(`Installing ${title}...`);
|
||||
if (ele.dataset.slug) {
|
||||
let obj = await getMarketplaceObj(ele.dataset.slug);
|
||||
installExtension(obj, ele.dataset.slug)
|
||||
.then((code) => {
|
||||
let notifMessage = "";
|
||||
let timeout = 2000;
|
||||
switch (code) {
|
||||
case 0:
|
||||
notifMessage = `Installed ${title} Successfully!`;
|
||||
break;
|
||||
case 1:
|
||||
notifMessage = `${title} is already installed!`;
|
||||
timeout = 0;
|
||||
break;
|
||||
case -1:
|
||||
// We should NEVER get here, but just in case.
|
||||
notifMessage = `Failed to install ${title}!`;
|
||||
break;
|
||||
}
|
||||
setTimeout(() => {
|
||||
notification.dismiss(installNotif);
|
||||
notification.options.duration = 2000;
|
||||
notification.success(notifMessage);
|
||||
notification.options.duration = 999999;
|
||||
}, timeout);
|
||||
})
|
||||
.catch(() => {
|
||||
notification.dismiss(installNotif);
|
||||
notification.options.duration = 2000;
|
||||
notification.error(`Failed to install ${title}!`);
|
||||
notification.options.duration = 999999;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
type MarketplaceItem = {
|
||||
title: string;
|
||||
version: string | number;
|
||||
image: string;
|
||||
script: string;
|
||||
scriptBtoa: string | null;
|
||||
};
|
||||
|
||||
async function getMarketplaceObj(slug: string): Promise<ExtensionMetadata> {
|
||||
const manifest = (marketplaceManifest as unknown as { [key: string]: MarketplaceItem })[slug];
|
||||
manifest.scriptBtoa = btoa(await fetch(manifest.script).then((res) => res.text()));
|
||||
return manifest;
|
||||
}
|
||||
|
||||
interface ExtensionMetadata {
|
||||
title: string;
|
||||
version: string | number;
|
||||
script: string;
|
||||
scriptBtoa: string | null;
|
||||
}
|
||||
|
||||
enum EXT_RETURN {
|
||||
INSTALL_SUCCESS = 0,
|
||||
INSTALL_FAILED = -1,
|
||||
ALREADY_INSTALLED = 1,
|
||||
}
|
||||
|
||||
async function installExtension(ext: ExtensionMetadata, slug: string) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const request = window.indexedDB.open("AluDB", 1);
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result;
|
||||
if (!db.objectStoreNames.contains("InstalledExtensions")) {
|
||||
db.createObjectStore("InstalledExtensions", { keyPath: "slug" });
|
||||
}
|
||||
};
|
||||
request.onsuccess = async (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result;
|
||||
const transaction = db.transaction("InstalledExtensions", "readwrite");
|
||||
const store = transaction.objectStore("InstalledExtensions");
|
||||
const extensionObject = {
|
||||
slug: slug,
|
||||
title: ext.title,
|
||||
version: ext.version,
|
||||
script: ext.scriptBtoa,
|
||||
};
|
||||
// Check if the key already exists in the IDB
|
||||
let slugCheck = store.get(slug);
|
||||
slugCheck.onsuccess = async () => {
|
||||
if (slugCheck.result != undefined) {
|
||||
resolve(EXT_RETURN.ALREADY_INSTALLED);
|
||||
} else {
|
||||
const addRequest = store.add(extensionObject);
|
||||
addRequest.onerror = () => {
|
||||
console.error(`Error installing ${slug}!`);
|
||||
reject(EXT_RETURN.INSTALL_FAILED);
|
||||
};
|
||||
addRequest.onsuccess = () => {
|
||||
resolve(EXT_RETURN.INSTALL_SUCCESS);
|
||||
};
|
||||
}
|
||||
};
|
||||
slugCheck.onerror = () => {
|
||||
console.error("Error checking install status!");
|
||||
reject(EXT_RETURN.INSTALL_FAILED);
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function InitIDB() {
|
||||
if (!window.indexedDB) {
|
||||
console.error("This browser doesn't support IndexedDB");
|
||||
return;
|
||||
}
|
||||
const request = window.indexedDB.open("AluDB", 1);
|
||||
request.onerror = (event: Event) => {
|
||||
console.error("Database error: " + (event.target as any).errorCode);
|
||||
};
|
||||
request.onsuccess = () => {
|
||||
console.log("Database opened successfully");
|
||||
};
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result;
|
||||
if (!db.objectStoreNames.contains("InstalledExtensions")) {
|
||||
db.createObjectStore("InstalledExtensions", { keyPath: "slug" });
|
||||
console.log("Database setup complete");
|
||||
}
|
||||
};
|
||||
}
|
||||
InitIDB();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.title-desc {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.marketplace-ext-grid {
|
||||
display: grid;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
color: var(--text-color);
|
||||
display: grid;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
grid-template-columns: repeat(auto-fill, minmax(45%, 1fr));
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
|
|
@ -204,13 +58,26 @@ type MarketplaceItem = {
|
|||
border: 3px solid var(--accent-color);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
width: 180px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.marketplace-item-image {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
.marketplace-install-btn {
|
||||
.marketplace-item-desc {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
height: 40px;
|
||||
}
|
||||
.marketplace-btn {
|
||||
width: 100%;
|
||||
background-color: var(--accent-color);
|
||||
color: var(--text-color);
|
||||
|
|
@ -219,5 +86,23 @@ type MarketplaceItem = {
|
|||
padding: 3px;
|
||||
cursor: pointer;
|
||||
font-family: "Varela Round", sans-serif;
|
||||
border-radius: 15px;
|
||||
margin-top: 10px;
|
||||
transition: 250ms ease-in-out;
|
||||
}
|
||||
.marketplace-btn.installed {
|
||||
filter: brightness(0.8);
|
||||
cursor: auto;
|
||||
}
|
||||
#support-warning {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 50px;
|
||||
height: 20px;
|
||||
color: #ff6923;
|
||||
text-align: center;
|
||||
}
|
||||
.btn-hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -55,8 +55,15 @@ export function getStaticPaths() {
|
|||
<div id="current-content"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
import IDBManager from "@components/ts/IDBManager";
|
||||
let load = IDBManager.loadIDB("AluDB", 1);
|
||||
load.onsuccess = () => {
|
||||
window.idb = IDBManager.GetIDB();
|
||||
};
|
||||
</script>
|
||||
<script is:inline>
|
||||
window.loadedContentStorage = {};
|
||||
loadedContentStorage = {};
|
||||
window.currentlySelectedTab;
|
||||
function settingsLoad() {
|
||||
document.addEventListener("astro:before-swap", () => {
|
||||
|
|
@ -67,7 +74,7 @@ export function getStaticPaths() {
|
|||
Array.from(document.getElementsByClassName("setting-tab")).forEach((tab) => {
|
||||
let contentToLoad = document.getElementById("content-" + tab.id);
|
||||
if (contentToLoad) {
|
||||
window.loadedContentStorage[tab.id] = contentToLoad.innerHTML;
|
||||
loadedContentStorage[tab.id] = contentToLoad.innerHTML;
|
||||
contentToLoad.remove();
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +90,7 @@ export function getStaticPaths() {
|
|||
if (currentContent) {
|
||||
currentContent.style.opacity = "0";
|
||||
setTimeout(() => {
|
||||
currentContent.innerHTML = window.loadedContentStorage[tabID];
|
||||
currentContent.innerHTML = loadedContentStorage[tabID];
|
||||
currentContent.style.opacity = "1";
|
||||
document.dispatchEvent(new CustomEvent("setting-tabChange", { detail: tabID }));
|
||||
document.dispatchEvent(new CustomEvent("setting-tabLoad", { detail: tabID }));
|
||||
|
|
@ -193,27 +200,44 @@ export function getStaticPaths() {
|
|||
});
|
||||
}
|
||||
|
||||
function addThemeToDropdown(extension) {
|
||||
let dropdown = document.getElementById("dropdown__selected-theme-menu");
|
||||
if (dropdown) {
|
||||
let themeItem = document.createElement("li");
|
||||
themeItem.classList.add("dropdown-item");
|
||||
themeItem.dataset.setting = extension.themeName;
|
||||
themeItem.textContent = extension.title;
|
||||
dropdown.appendChild(themeItem);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
addThemeToDropdown(extension);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("setting-tabChange", determineListener);
|
||||
|
||||
loadContent("setting-tab-proxy");
|
||||
|
||||
function setupCustomizationSettings() {
|
||||
applySavedLocalStorage("alu__selectedTheme", "dropdown__selected-theme");
|
||||
applySavedLocalStorage("alu__selectedLanguage", "dropdown__selected-language");
|
||||
let themeDropdown = document.getElementById("dropdown__selected-theme-menu");
|
||||
let languageDropdown = document.getElementById("dropdown__selected-language-menu");
|
||||
applyDropdownEventListeners(
|
||||
themeDropdown,
|
||||
"dropdown__selected-theme",
|
||||
"alu__selectedTheme",
|
||||
changeTheme
|
||||
);
|
||||
applyDropdownEventListeners(
|
||||
languageDropdown,
|
||||
"dropdown__selected-language",
|
||||
"alu__selectedLanguage",
|
||||
navigateToNewLangaugePage
|
||||
);
|
||||
let store = window.idb.transaction("InstalledExtensions", "readonly").objectStore("InstalledExtensions");
|
||||
store.getAll().onsuccess = (event) => {
|
||||
const result = event.target.result;
|
||||
if (result) {
|
||||
result.forEach((extension) => {
|
||||
if (extension.type === "theme" && extension.themeName) {
|
||||
// Create a dropdown item for the theme
|
||||
addThemeToDropdown(extension);
|
||||
}
|
||||
});
|
||||
}
|
||||
applySavedLocalStorage("alu__selectedTheme", "dropdown__selected-theme");
|
||||
applySavedLocalStorage("alu__selectedLanguage", "dropdown__selected-language");
|
||||
let themeDropdown = document.getElementById("dropdown__selected-theme-menu");
|
||||
let languageDropdown = document.getElementById("dropdown__selected-language-menu");
|
||||
applyDropdownEventListeners(themeDropdown, "dropdown__selected-theme", "alu__selectedTheme", changeTheme);
|
||||
applyDropdownEventListeners(languageDropdown, "dropdown__selected-language", "alu__selectedLanguage", navigateToNewLangaugePage);
|
||||
};
|
||||
}
|
||||
|
||||
function setupCloakingSettings() {
|
||||
|
|
@ -313,18 +337,15 @@ export function getStaticPaths() {
|
|||
let bareURLInput = document.getElementById("bare-url-input");
|
||||
let savedSearxngUrl = localStorage.getItem("alu__searxngUrl");
|
||||
if (savedSearxngUrl != null) {
|
||||
if (savedSearxngUrl == "")
|
||||
localStorage.setItem("alu__searxngUrl", "https://searxng.site/");
|
||||
if (savedSearxngUrl == "") localStorage.setItem("alu__searxngUrl", "https://searxng.site/");
|
||||
searxngUrlInput.value = localStorage.getItem("alu__searxngUrl");
|
||||
}
|
||||
const useWss = location.protocol == "https:";
|
||||
const webSocketProtocol = useWss ? "wss://" : "ws://";
|
||||
let savedWispUrl = localStorage.getItem("alu__wispUrl");
|
||||
if (savedWispUrl == null || savedWispUrl == "")
|
||||
localStorage.setItem("alu__wispUrl", webSocketProtocol + location.host + "/wisp/");
|
||||
if (savedWispUrl == null || savedWispUrl == "") localStorage.setItem("alu__wispUrl", webSocketProtocol + location.host + "/wisp/");
|
||||
let savedBareUrl = localStorage.getItem("alu__bareUrl");
|
||||
if (savedBareUrl == null || savedBareUrl == "")
|
||||
localStorage.setItem("alu__bareUrl", location.origin + "/bare/");
|
||||
if (savedBareUrl == null || savedBareUrl == "") localStorage.setItem("alu__bareUrl", location.origin + "/bare/");
|
||||
wispURLInput.value = localStorage.getItem("alu__wispUrl");
|
||||
bareURLInput.value = localStorage.getItem("alu__bareUrl");
|
||||
// Proxy settings
|
||||
|
|
@ -332,22 +353,11 @@ export function getStaticPaths() {
|
|||
applyInputListeners(wispURLInput, "alu__wispUrl");
|
||||
applyInputListeners(bareURLInput, "alu__bareUrl");
|
||||
|
||||
[selectedProxyDropdown, openWithDropdown, currentTransportDropdown].forEach(
|
||||
(dropdown) => {
|
||||
let dropdownButton = document.getElementById(dropdown.id.replace("-menu", ""));
|
||||
applyDropdownEventListeners(
|
||||
dropdown,
|
||||
dropdownButton.id,
|
||||
dropdownButton.dataset.localStorageKey
|
||||
);
|
||||
}
|
||||
);
|
||||
applyDropdownEventListeners(
|
||||
searchEngineDropdown,
|
||||
"dropdown__search-engine",
|
||||
"alu__search_engine",
|
||||
checkSearxng
|
||||
);
|
||||
[selectedProxyDropdown, openWithDropdown, currentTransportDropdown].forEach((dropdown) => {
|
||||
let dropdownButton = document.getElementById(dropdown.id.replace("-menu", ""));
|
||||
applyDropdownEventListeners(dropdown, dropdownButton.id, dropdownButton.dataset.localStorageKey);
|
||||
});
|
||||
applyDropdownEventListeners(searchEngineDropdown, "dropdown__search-engine", "alu__search_engine", checkSearxng);
|
||||
checkSearxng();
|
||||
} else if (event.detail == "setting-tab-customization") {
|
||||
setupCustomizationSettings();
|
||||
|
|
|
|||
118
src/pages/game/[game].astro
Normal file
118
src/pages/game/[game].astro
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
---
|
||||
export const prerender = false;
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import games from "../../json/games.json";
|
||||
|
||||
let gamesList = games as GameList;
|
||||
|
||||
// get the current game based on the information in the url
|
||||
let game = Astro.params.game;
|
||||
if (!game) {
|
||||
Astro.redirect("/en/games/");
|
||||
return;
|
||||
}
|
||||
|
||||
function isValidGameKey(key: string) {
|
||||
return key in gamesList;
|
||||
}
|
||||
|
||||
let gameData = isValidGameKey(game) ? gamesList[game] : null;
|
||||
|
||||
if (!gameData) {
|
||||
return Astro.redirect("/en/games/");
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
<Layout title="Game">
|
||||
<div id="main-content">
|
||||
<div class="game-container">
|
||||
{gameData.unity ?
|
||||
<iframe scrolling="no" src=`/unity/${gameData.slug}` title={gameData.name} id="game-frame"/> :
|
||||
<iframe scrolling="no" src=`/games/${gameData.slug}` title={gameData.name} id="game-frame"/>
|
||||
}
|
||||
<div class="game-info">
|
||||
<div class="game-info-top">
|
||||
<p class="game-title">{gameData.name}</p>
|
||||
<img src="/img/games/fullscreen.svg" alt="Fullscreen" id="game-fullscreen" class="icn">
|
||||
</div>
|
||||
<div class="game-info-bottom">
|
||||
<p class="game-desc">{gameData.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
#main-content {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.game-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
#game-frame {
|
||||
aspect-ratio: 16 / 8;
|
||||
height: 80vh;
|
||||
width: 1350px;
|
||||
border: 0;
|
||||
}
|
||||
.game-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
background-color: var(--background-highlight);
|
||||
width: 1350px;
|
||||
padding: 1rem;
|
||||
padding-top: 0;
|
||||
}
|
||||
.game-info-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.icn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.game-title {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
.game-desc {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
let iframe = document.getElementById("game-frame") as HTMLIFrameElement;
|
||||
iframe.contentWindow?.focus();
|
||||
|
||||
iframe?.addEventListener("load", () => {
|
||||
iframe.contentWindow?.focus();
|
||||
});
|
||||
|
||||
iframe.addEventListener("click", () => {
|
||||
iframe.contentWindow?.focus();
|
||||
})
|
||||
|
||||
document.addEventListener("astro:after-swap", () => {
|
||||
let iframe = document.getElementById("game-frame") as HTMLIFrameElement;
|
||||
iframe?.contentWindow?.focus();
|
||||
})
|
||||
|
||||
let fullscreen = document.getElementById("game-fullscreen") as HTMLImageElement;
|
||||
|
||||
fullscreen.addEventListener("click", () => {
|
||||
if (iframe.requestFullscreen) {
|
||||
iframe.requestFullscreen();
|
||||
iframe.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -8,25 +8,16 @@ import { ViewTransitions } from "astro:transitions";
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="title" content="Alu" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Alu is a sleek web proxy supporting multiple standards of communication, and wide levels of customization."
|
||||
/>
|
||||
<meta name="description" content="Alu is a sleek web proxy supporting multiple standards of communication, and wide levels of customization." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://aluu.xyz" />
|
||||
<meta property="og:title" content="Alu" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Alu is a sleek web proxy supporting multiple standards of communication, and wide levels of customization."
|
||||
/>
|
||||
<meta property="og:description" content="Alu is a sleek web proxy supporting multiple standards of communication, and wide levels of customization." />
|
||||
<meta property="og:image" content="/logo.png" />
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://aluu.xyz" />
|
||||
<meta property="twitter:title" content="Alu" />
|
||||
<meta
|
||||
property="twitter:description"
|
||||
content="Alu is a sleek web proxy supporting multiple standards of communication, and wide levels of customization."
|
||||
/>
|
||||
<meta property="twitter:description" content="Alu is a sleek web proxy supporting multiple standards of communication, and wide levels of customization." />
|
||||
<meta property="twitter:image" content="/logo.png" />
|
||||
<ViewTransitions />
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -8,30 +8,21 @@ import Layout from "src/layouts/Layout.astro";
|
|||
<h1 class="title-text">Privacy Policy</h1>
|
||||
|
||||
<p>
|
||||
Alu Project operates the <Link href="https://aluu.xyz" newTab content="https://aluu.xyz" /> website,
|
||||
which provides this Web Proxy service.
|
||||
Alu Project operates the <Link href="https://aluu.xyz" newTab content="https://aluu.xyz" /> website, which provides this Web Proxy service.
|
||||
</p>
|
||||
<p>This page is used to inform website visitors regarding our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service, the Alu website.</p>
|
||||
<p>
|
||||
If you choose to use our Service, then you agree to the collection and use of information in relation with this policy. The Personal Information that we collect are used for providing and
|
||||
improving the Service. We will not use or share your information with anyone except as described in this Privacy Policy.
|
||||
</p>
|
||||
<p>
|
||||
This page is used to inform website visitors regarding our policies with the collection, use,
|
||||
and disclosure of Personal Information if anyone decided to use our Service, the Alu website.
|
||||
</p>
|
||||
<p>
|
||||
If you choose to use our Service, then you agree to the collection and use of information in
|
||||
relation with this policy. The Personal Information that we collect are used for providing and
|
||||
improving the Service. We will not use or share your information with anyone except as
|
||||
described in this Privacy Policy.
|
||||
</p>
|
||||
<p>
|
||||
The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions,
|
||||
which is accessible at <Link href="https://aluu.xyz" newTab content="https://aluu.xyz" />,
|
||||
unless otherwise defined in this Privacy Policy.
|
||||
The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at <Link href="https://aluu.xyz" newTab content="https://aluu.xyz" />, unless
|
||||
otherwise defined in this Privacy Policy.
|
||||
</p>
|
||||
<h2><strong>Log Data</strong></h2>
|
||||
<p>
|
||||
Whenever you visit our Service, we potentially collect information that your browser sends to
|
||||
us which we call Log Data. This Log Data may include information such as your computer's IP
|
||||
address, User Agent, pages of our Service that you visit, the time and date of your visit, and
|
||||
other statistics.
|
||||
Whenever you visit our Service, we potentially collect information that your browser sends to us which we call Log Data. This Log Data may include information such as your computer's IP address,
|
||||
User Agent, pages of our Service that you visit, the time and date of your visit, and other statistics.
|
||||
</p>
|
||||
<h2><strong>Service Providers</strong></h2>
|
||||
<p>We may employ third-party companies and individuals due to the following reasons:</p>
|
||||
|
|
@ -42,45 +33,33 @@ import Layout from "src/layouts/Layout.astro";
|
|||
<li>To assist us in analyzing how our Service is used.</li>
|
||||
</ul>
|
||||
<p>
|
||||
We want to inform our Service users that these third parties may have access to your Personal
|
||||
Information. The reason is to perform the tasks assigned to them on our behalf. However, they
|
||||
are obligated not to disclose or use the information for any other purpose.
|
||||
We want to inform our Service users that these third parties may have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are
|
||||
obligated not to disclose or use the information for any other purpose.
|
||||
</p>
|
||||
<h2><strong>Security</strong></h2>
|
||||
<p>
|
||||
We value your trust in providing us your Personal Information, thus we are striving to use
|
||||
commercially acceptable means of protecting it. But remember that no method of transmission
|
||||
over the internet, or method of electronic storage is 100% secure and reliable, and we cannot
|
||||
guarantee its absolute security.
|
||||
We value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the
|
||||
internet, or method of electronic storage is 100% secure and reliable, and we cannot guarantee its absolute security.
|
||||
</p>
|
||||
<h2><strong>Links to Other Sites</strong></h2>
|
||||
<p>
|
||||
Our Service may contain links to other sites. If you click on a third-party link, you will be
|
||||
directed to that site. Note that these external sites are not operated by us. Therefore, we
|
||||
strongly advise you to review the Privacy Policy of these websites. We have no control over,
|
||||
and assume no responsibility for the content, privacy policies, or practices of any
|
||||
third-party sites or services.
|
||||
Our Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by us. Therefore, we strongly
|
||||
advise you to review the Privacy Policy of these websites. We have no control over, and assume no responsibility for the content, privacy policies, or practices of any third-party sites or
|
||||
services.
|
||||
</p>
|
||||
<h2><strong>Children's Privacy</strong></h2>
|
||||
<p>
|
||||
Our Services do not address anyone under the age of 13. We do not knowingly collect personal
|
||||
identifiable information from children under 13. In the case we discover that a child under 13
|
||||
has provided us with personal information, we immediately delete this from our servers. If you
|
||||
are a parent or guardian and you are aware that your child has provided us with personal
|
||||
information, please contact us so that we will be able to do necessary actions.
|
||||
Our Services do not address anyone under the age of 13. We do not knowingly collect personal identifiable information from children under 13. In the case we discover that a child under 13 has
|
||||
provided us with personal information, we immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information,
|
||||
please contact us so that we will be able to do necessary actions.
|
||||
</p>
|
||||
<h2><strong>Changes to This Privacy Policy</strong></h2>
|
||||
<p>
|
||||
We may update our Privacy Policy from time to time. Thus, we advise you to review this page
|
||||
periodically for any changes. We will notify you of any changes by posting the new Privacy
|
||||
Policy on this page. These changes are effective immediately, after they are posted on this
|
||||
page.
|
||||
We may update our Privacy Policy from time to time. Thus, we advise you to review this page periodically for any changes. We will notify you of any changes by posting the new Privacy Policy on
|
||||
this page. These changes are effective immediately, after they are posted on this page.
|
||||
</p>
|
||||
<h2><strong>Contact Us</strong></h2>
|
||||
<p>
|
||||
If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact
|
||||
us.
|
||||
</p>
|
||||
<p>If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us.</p>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,215 +3,160 @@ import Layout from "src/layouts/Layout.astro";
|
|||
---
|
||||
|
||||
<Layout title="Terms and Conditions | Alu">
|
||||
<div id="main-content">
|
||||
<h2><strong>Terms and Conditions</strong></h2>
|
||||
<div id="main-content">
|
||||
<h2><strong>Terms and Conditions</strong></h2>
|
||||
|
||||
<p>Welcome to Alu!</p>
|
||||
|
||||
<p>
|
||||
These terms and conditions outline the rules and regulations for the use of Alu Project's
|
||||
Website, located at https://aluu.xyz.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
By accessing this website we assume you accept these terms and conditions. Do not continue to
|
||||
use Alu if you do not agree to take all of the terms and conditions stated on this page.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The following terminology applies to these Terms and Conditions, Privacy Statement and
|
||||
Disclaimer Notice and all Agreements: "Client", "You" and "Your" refers to you, the person log
|
||||
on this website and compliant to the Company's terms and conditions. "The Company", "Ourselves",
|
||||
"We", "Our" and "Us", refers to our Company. "Party", "Parties", or "Us", refers to both the
|
||||
Client and ourselves. All terms refer to the offer, acceptance and consideration of payment
|
||||
necessary to undertake the process of our assistance to the Client in the most appropriate
|
||||
manner for the express purpose of meeting the Client's needs in respect of provision of the
|
||||
Company's stated services, in accordance with and subject to, prevailing law of us. Any use of
|
||||
the above terminology or other words in the singular, plural, capitalization and/or he/she or
|
||||
they, are taken as interchangeable and therefore as referring to same.
|
||||
</p>
|
||||
|
||||
<h3><strong>Cookies</strong></h3>
|
||||
|
||||
<p>
|
||||
We employ the use of cookies. By accessing Alu, you agreed to use cookies in agreement with the
|
||||
Alu Project's Privacy Policy.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Most interactive websites use cookies to let us retrieve the user's details for each visit.
|
||||
Cookies are used by our website to enable the functionality of certain areas to make it easier
|
||||
for people visiting our website. Some of our affiliate/advertising partners may also use
|
||||
cookies.
|
||||
</p>
|
||||
|
||||
<h3><strong>License</strong></h3>
|
||||
|
||||
<p>
|
||||
Unless otherwise stated, Alu Project and/or its licensors own the intellectual property rights
|
||||
for all material on Alu. All intellectual property rights are reserved. You may access this from
|
||||
Alu for your own personal use subjected to restrictions set in these terms and conditions.
|
||||
</p>
|
||||
|
||||
<p>You must not:</p>
|
||||
<ul>
|
||||
<li>Republish material from Alu</li>
|
||||
<li>Sell, rent or sub-license material from Alu</li>
|
||||
<li>Reproduce, duplicate or copy material from Alu</li>
|
||||
<li>Redistribute content from Alu</li>
|
||||
</ul>
|
||||
|
||||
<p>This Agreement shall begin on the date hereof.</p>
|
||||
|
||||
<h3><strong>Hyperlinking to our Content</strong></h3>
|
||||
|
||||
<p>The following organizations may link to our Website without prior written approval:</p>
|
||||
|
||||
<ul>
|
||||
<li>Government agencies;</li>
|
||||
<li>Search engines;</li>
|
||||
<li>News organizations;</li>
|
||||
<li>
|
||||
Online directory distributors may link to our Website in the same manner as they hyperlink to
|
||||
the Websites of other listed businesses; and
|
||||
</li>
|
||||
<li>
|
||||
System wide Accredited Businesses except soliciting non-profit organizations, charity shopping
|
||||
malls, and charity fundraising groups which may not hyperlink to our Web site.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
These organizations may link to our home page, to publications or to other Website information
|
||||
so long as the link: (a) is not in any way deceptive; (b) does not falsely imply sponsorship,
|
||||
endorsement or approval of the linking party and its products and/or services; and (c) fits
|
||||
within the context of the linking party's site.
|
||||
</p>
|
||||
|
||||
<p>We may consider and approve other link requests from the following types of organizations:</p>
|
||||
|
||||
<ul>
|
||||
<li>commonly-known consumer and/or business information sources;</li>
|
||||
<li>dot.com community sites;</li>
|
||||
<li>associations or other groups representing charities;</li>
|
||||
<li>online directory distributors;</li>
|
||||
<li>internet portals;</li>
|
||||
<li>accounting, law and consulting firms; and</li>
|
||||
<li>educational institutions and trade associations.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
We will approve link requests from these organizations if we decide that: (a) the link would not
|
||||
make us look unfavorably to ourselves or to our accredited businesses; (b) the organization does
|
||||
not have any negative records with us; (c) the benefit to us from the visibility of the
|
||||
hyperlink compensates the absence of Alu Project; and (d) the link is in the context of general
|
||||
resource information.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
These organizations may link to our home page so long as the link: (a) is not in any way
|
||||
deceptive; (b) does not falsely imply sponsorship, endorsement or approval of the linking party
|
||||
and its products or services; and (c) fits within the context of the linking party's site.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you are one of the organizations listed in paragraph 2 above and are interested in linking to
|
||||
our website, you must inform us by sending an e-mail to Alu Project. Please include your name,
|
||||
your organization name, contact information as well as the URL of your site, a list of any URLs
|
||||
from which you intend to link to our Website, and a list of the URLs on our site to which you
|
||||
would like to link. Wait 2-3 weeks for a response.
|
||||
</p>
|
||||
|
||||
<p>Approved organizations may hyperlink to our Website as follows:</p>
|
||||
|
||||
<ul>
|
||||
<li>By use of our corporate name; or</li>
|
||||
<li>By use of the uniform resource locator being linked to; or</li>
|
||||
<li>
|
||||
By use of any other description of our Website being linked to that makes sense within the
|
||||
context and format of content on the linking party's site.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
No use of Alu Project's logo or other artwork will be allowed for linking absent a trademark
|
||||
license agreement.
|
||||
</p>
|
||||
|
||||
<h3><strong>iFrames</strong></h3>
|
||||
|
||||
<p>
|
||||
Without prior approval and written permission, you may not create frames around our Webpages
|
||||
that alter in any way the visual presentation or appearance of our Website.
|
||||
</p>
|
||||
|
||||
<h3><strong>Content Liability</strong></h3>
|
||||
|
||||
<p>
|
||||
We shall not be hold responsible for any content that appears on your Website. You agree to
|
||||
protect and defend us against all claims that is rising on your Website. No link(s) should
|
||||
appear on any Website that may be interpreted as libelous, obscene or criminal, or which
|
||||
infringes, otherwise violates, or advocates the infringement or other violation of, any third
|
||||
party rights.
|
||||
</p>
|
||||
|
||||
<h3><strong>Reservation of Rights</strong></h3>
|
||||
|
||||
<p>
|
||||
We reserve the right to request that you remove all links or any particular link to our Website.
|
||||
You approve to immediately remove all links to our Website upon request. We also reserve the
|
||||
right to amen these terms and conditions and it's linking policy at any time. By continuously
|
||||
linking to our Website, you agree to be bound to and follow these linking terms and conditions.
|
||||
</p>
|
||||
|
||||
<h3><strong>Removal of links from our website</strong></h3>
|
||||
|
||||
<p>
|
||||
If you find any link on our Website that is offensive for any reason, you are free to contact
|
||||
and inform us any moment. We will consider requests to remove links but we are not obligated to
|
||||
or so or to respond to you directly.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We do not ensure that the information on this website is correct, we do not warrant its
|
||||
completeness or accuracy; nor do we promise to ensure that the website remains available or that
|
||||
the material on the website is kept up to date.
|
||||
</p>
|
||||
|
||||
<h3><strong>Disclaimer</strong></h3>
|
||||
|
||||
<p>
|
||||
To the maximum extent permitted by applicable law, we exclude all representations, warranties
|
||||
and conditions relating to our website and the use of this website. Nothing in this disclaimer
|
||||
will:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>limit or exclude our or your liability for death or personal injury;</li>
|
||||
<li>limit or exclude our or your liability for fraud or fraudulent misrepresentation;</li>
|
||||
<li>
|
||||
limit any of our or your liabilities in any way that is not permitted under applicable law; or
|
||||
</li>
|
||||
<li>exclude any of our or your liabilities that may not be excluded under applicable law.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
The limitations and prohibitions of liability set in this Section and elsewhere in this
|
||||
disclaimer: (a) are subject to the preceding paragraph; and (b) govern all liabilities arising
|
||||
under the disclaimer, including liabilities arising in contract, in tort and for breach of
|
||||
statutory duty.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
As long as the website and the information and services on the website are provided free of
|
||||
charge, we will not be liable for any loss or damage of any nature.
|
||||
</p>
|
||||
</div>
|
||||
<p>Welcome to Alu!</p>
|
||||
|
||||
<p>These terms and conditions outline the rules and regulations for the use of Alu Project's Website, located at https://aluu.xyz.</p>
|
||||
|
||||
<p>By accessing this website we assume you accept these terms and conditions. Do not continue to use Alu if you do not agree to take all of the terms and conditions stated on this page.</p>
|
||||
|
||||
<p>
|
||||
The following terminology applies to these Terms and Conditions, Privacy Statement and Disclaimer Notice and all Agreements: "Client", "You" and "Your" refers to you, the person log on this
|
||||
website and compliant to the Company's terms and conditions. "The Company", "Ourselves", "We", "Our" and "Us", refers to our Company. "Party", "Parties", or "Us", refers to both the Client and
|
||||
ourselves. All terms refer to the offer, acceptance and consideration of payment necessary to undertake the process of our assistance to the Client in the most appropriate manner for the express
|
||||
purpose of meeting the Client's needs in respect of provision of the Company's stated services, in accordance with and subject to, prevailing law of us. Any use of the above terminology or other
|
||||
words in the singular, plural, capitalization and/or he/she or they, are taken as interchangeable and therefore as referring to same.
|
||||
</p>
|
||||
|
||||
<h3><strong>Cookies</strong></h3>
|
||||
|
||||
<p>We employ the use of cookies. By accessing Alu, you agreed to use cookies in agreement with the Alu Project's Privacy Policy.</p>
|
||||
|
||||
<p>
|
||||
Most interactive websites use cookies to let us retrieve the user's details for each visit. Cookies are used by our website to enable the functionality of certain areas to make it easier for
|
||||
people visiting our website. Some of our affiliate/advertising partners may also use cookies.
|
||||
</p>
|
||||
|
||||
<h3><strong>License</strong></h3>
|
||||
|
||||
<p>
|
||||
Unless otherwise stated, Alu Project and/or its licensors own the intellectual property rights for all material on Alu. All intellectual property rights are reserved. You may access this from
|
||||
Alu for your own personal use subjected to restrictions set in these terms and conditions.
|
||||
</p>
|
||||
|
||||
<p>You must not:</p>
|
||||
<ul>
|
||||
<li>Republish material from Alu</li>
|
||||
<li>Sell, rent or sub-license material from Alu</li>
|
||||
<li>Reproduce, duplicate or copy material from Alu</li>
|
||||
<li>Redistribute content from Alu</li>
|
||||
</ul>
|
||||
|
||||
<p>This Agreement shall begin on the date hereof.</p>
|
||||
|
||||
<h3><strong>Hyperlinking to our Content</strong></h3>
|
||||
|
||||
<p>The following organizations may link to our Website without prior written approval:</p>
|
||||
|
||||
<ul>
|
||||
<li>Government agencies;</li>
|
||||
<li>Search engines;</li>
|
||||
<li>News organizations;</li>
|
||||
<li>Online directory distributors may link to our Website in the same manner as they hyperlink to the Websites of other listed businesses; and</li>
|
||||
<li>System wide Accredited Businesses except soliciting non-profit organizations, charity shopping malls, and charity fundraising groups which may not hyperlink to our Web site.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
These organizations may link to our home page, to publications or to other Website information so long as the link: (a) is not in any way deceptive; (b) does not falsely imply sponsorship,
|
||||
endorsement or approval of the linking party and its products and/or services; and (c) fits within the context of the linking party's site.
|
||||
</p>
|
||||
|
||||
<p>We may consider and approve other link requests from the following types of organizations:</p>
|
||||
|
||||
<ul>
|
||||
<li>commonly-known consumer and/or business information sources;</li>
|
||||
<li>dot.com community sites;</li>
|
||||
<li>associations or other groups representing charities;</li>
|
||||
<li>online directory distributors;</li>
|
||||
<li>internet portals;</li>
|
||||
<li>accounting, law and consulting firms; and</li>
|
||||
<li>educational institutions and trade associations.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
We will approve link requests from these organizations if we decide that: (a) the link would not make us look unfavorably to ourselves or to our accredited businesses; (b) the organization does
|
||||
not have any negative records with us; (c) the benefit to us from the visibility of the hyperlink compensates the absence of Alu Project; and (d) the link is in the context of general resource
|
||||
information.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
These organizations may link to our home page so long as the link: (a) is not in any way deceptive; (b) does not falsely imply sponsorship, endorsement or approval of the linking party and its
|
||||
products or services; and (c) fits within the context of the linking party's site.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you are one of the organizations listed in paragraph 2 above and are interested in linking to our website, you must inform us by sending an e-mail to Alu Project. Please include your name,
|
||||
your organization name, contact information as well as the URL of your site, a list of any URLs from which you intend to link to our Website, and a list of the URLs on our site to which you
|
||||
would like to link. Wait 2-3 weeks for a response.
|
||||
</p>
|
||||
|
||||
<p>Approved organizations may hyperlink to our Website as follows:</p>
|
||||
|
||||
<ul>
|
||||
<li>By use of our corporate name; or</li>
|
||||
<li>By use of the uniform resource locator being linked to; or</li>
|
||||
<li>By use of any other description of our Website being linked to that makes sense within the context and format of content on the linking party's site.</li>
|
||||
</ul>
|
||||
|
||||
<p>No use of Alu Project's logo or other artwork will be allowed for linking absent a trademark license agreement.</p>
|
||||
|
||||
<h3><strong>iFrames</strong></h3>
|
||||
|
||||
<p>Without prior approval and written permission, you may not create frames around our Webpages that alter in any way the visual presentation or appearance of our Website.</p>
|
||||
|
||||
<h3><strong>Content Liability</strong></h3>
|
||||
|
||||
<p>
|
||||
We shall not be hold responsible for any content that appears on your Website. You agree to protect and defend us against all claims that is rising on your Website. No link(s) should appear on
|
||||
any Website that may be interpreted as libelous, obscene or criminal, or which infringes, otherwise violates, or advocates the infringement or other violation of, any third party rights.
|
||||
</p>
|
||||
|
||||
<h3><strong>Reservation of Rights</strong></h3>
|
||||
|
||||
<p>
|
||||
We reserve the right to request that you remove all links or any particular link to our Website. You approve to immediately remove all links to our Website upon request. We also reserve the
|
||||
right to amen these terms and conditions and it's linking policy at any time. By continuously linking to our Website, you agree to be bound to and follow these linking terms and conditions.
|
||||
</p>
|
||||
|
||||
<h3><strong>Removal of links from our website</strong></h3>
|
||||
|
||||
<p>
|
||||
If you find any link on our Website that is offensive for any reason, you are free to contact and inform us any moment. We will consider requests to remove links but we are not obligated to or
|
||||
so or to respond to you directly.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We do not ensure that the information on this website is correct, we do not warrant its completeness or accuracy; nor do we promise to ensure that the website remains available or that the
|
||||
material on the website is kept up to date.
|
||||
</p>
|
||||
|
||||
<h3><strong>Disclaimer</strong></h3>
|
||||
|
||||
<p>
|
||||
To the maximum extent permitted by applicable law, we exclude all representations, warranties and conditions relating to our website and the use of this website. Nothing in this disclaimer will:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>limit or exclude our or your liability for death or personal injury;</li>
|
||||
<li>limit or exclude our or your liability for fraud or fraudulent misrepresentation;</li>
|
||||
<li>limit any of our or your liabilities in any way that is not permitted under applicable law; or</li>
|
||||
<li>exclude any of our or your liabilities that may not be excluded under applicable law.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
The limitations and prohibitions of liability set in this Section and elsewhere in this disclaimer: (a) are subject to the preceding paragraph; and (b) govern all liabilities arising under the
|
||||
disclaimer, including liabilities arising in contract, in tort and for breach of statutory duty.
|
||||
</p>
|
||||
|
||||
<p>As long as the website and the information and services on the website are provided free of charge, we will not be liable for any loss or damage of any nature.</p>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
#main-content {
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
#main-content {
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
55
src/pages/unity/[game].astro
Normal file
55
src/pages/unity/[game].astro
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
export const prerender = false;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="w-full h-full">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Unity Content</title>
|
||||
<script is:inline src="/unity/UnityLoader.js"></script>
|
||||
<script is:inline src="/unity/instantiateUnity.js"></script>
|
||||
<link rel="stylesheet" href="/unity/index.css">
|
||||
</head>
|
||||
<body class="w-full h-full bg-slate-800 text-slate-200">
|
||||
<div
|
||||
id="loader"
|
||||
class="w-full h-full flex items-center justify-center flex-col gap-5"
|
||||
>
|
||||
<img src="/favicon.svg" class="w-10" />
|
||||
<svg
|
||||
class="animate-spin h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="h-full w-full hidden" id="error">
|
||||
<div
|
||||
class="w-full h-full flex items-center justify-center flex-col gap-5"
|
||||
>
|
||||
<img src="/favicon.svg" class="w-10" />
|
||||
<p>An error occurred.</p>
|
||||
<p class="underline cursor-pointer" onclick="location.reload()">
|
||||
Refresh
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="gameContainer" class="w-full h-full hidden bg-white"></div>
|
||||
</body>
|
||||
</html>
|
||||
51
src/types.d.ts
vendored
Normal file
51
src/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
type ExtType = "serviceWorker" | "theme" | "page";
|
||||
|
||||
type Extension = {
|
||||
name: string;
|
||||
script: string;
|
||||
type: ExtType;
|
||||
};
|
||||
|
||||
/*
|
||||
- title: The title of the extension
|
||||
- description: A description of the extension
|
||||
- version: The version of the extension (semver)
|
||||
- script: The script URL to be downloaded and saved into scriptCopy
|
||||
- entryNamespace: The namespace of the entry function for serviceWorker extensions
|
||||
- entryFunc: The name of the entry function for serviceWorker extensions
|
||||
- scriptCopy: The script contents of the extension btoa'd (null if not downloaded)
|
||||
- type: The type of extension, see ExtType.
|
||||
- themeName: The name of the theme that goes in the data attribute
|
||||
*/
|
||||
interface IExtensionMetadata {
|
||||
title: string;
|
||||
description: string;
|
||||
version: string;
|
||||
image: string;
|
||||
script: string;
|
||||
entryNamespace?: string;
|
||||
entryFunc?: string;
|
||||
scriptCopy?: string;
|
||||
type: ExtType;
|
||||
themeName?: string;
|
||||
}
|
||||
|
||||
type ExtensionMetadataJSON = Record<string, IExtensionMetadata>;
|
||||
|
||||
type InstallReturn = {
|
||||
code: EXT_RETURN;
|
||||
slug: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
type GameMetadata = {
|
||||
name: string,
|
||||
description?: string,
|
||||
image: string,
|
||||
slug: string,
|
||||
unity?: boolean
|
||||
}
|
||||
|
||||
type GameList = {
|
||||
[key: string]: GameMetadata;
|
||||
};
|
||||
|
|
@ -5,7 +5,8 @@
|
|||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@components/*": ["src/components/*"],
|
||||
"@i18n/*": ["src/i18n/*"]
|
||||
"@i18n/*": ["src/i18n/*"],
|
||||
"@json/*": ["src/json/*"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue