From 8e911f4f6e7661a921b4122977f8c501067d070a Mon Sep 17 00:00:00 2001
From: Russell2259 <84256826+Russell2259@users.noreply.github.com>
Date: Fri, 12 Jan 2024 11:22:55 -0700
Subject: [PATCH] patch and progress on ctc system
---
package.json | 5 +-
server/api.js | 39 +++--
static/assets/js/eastereggs.js | 5 +-
static/assets/js/main.js | 17 ++-
static/assets/js/utils.js | 6 +-
static/assets/js/utils/ctc.js | 250 ++++++++++++++++++++++++++-------
6 files changed, 258 insertions(+), 64 deletions(-)
diff --git a/package.json b/package.json
index e4e619d..422a1fc 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,9 @@
"prod": "node server prod",
"dev": "node server dev"
},
+ "engines": {
+ "node": "20.x"
+ },
"author": "Polaris Development Group",
"license": "GNU-3.0-or-later",
"dependencies": {
@@ -22,4 +25,4 @@
"mime": "*",
"uuid": "^9.0.1"
}
-}
+}
\ No newline at end of file
diff --git a/server/api.js b/server/api.js
index bbda961..035e734 100644
--- a/server/api.js
+++ b/server/api.js
@@ -6,20 +6,41 @@ import fs from 'node:fs';
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const packageFile = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json')));
const commits = await (await fetch(`https://api.github.com/repos/Skoolgq/Polaris/commits`)).json();
+var gitSupported = true;
/**
* @param {import('express').Express} app
*/
const routes = (app) => {
- app.get('/api/changelog', async (req, res) => res.json({
- version: packageFile.version || 'unknown',
- commit: {
- sha: childProcess.execSync('git rev-parse HEAD').toString().trim() || 'uknown',
- message: childProcess.execSync('git rev-list --format=%s --max-count=1 HEAD').toString().split('\n')[1].replace('changelog ', '') || 'unknown'
- },
- upToDate: (commits[0] ? ((commits[0].sha === childProcess.execSync('git rev-parse HEAD').toString().trim()) || false) : false),
- changelog: JSON.parse(fs.readFileSync(path.join(__dirname, '../static/assets/JSON/changelog.json')))
- }));
+ app.get('/api/changelog', async (req, res) => {
+ const changelog = {
+ version: packageFile.version || 'unknown',
+ changelog: JSON.parse(fs.readFileSync(path.join(__dirname, '../static/assets/JSON/changelog.json')))
+ }
+
+ if (gitSupported) try {
+ changelog.commit = {
+ sha: childProcess.execSync('git rev-parse HEAD').toString().trim() || 'uknown',
+ message: childProcess.execSync('git rev-list --format=%s --max-count=1 HEAD').toString().split('\n')[1].replace('changelog ', '') || 'unknown'
+ };
+
+ changelog.upToDate = (commits[0] ? ((commits[0].sha === childProcess.execSync('git rev-parse HEAD').toString().trim()) || false) : false);
+ } catch {
+ gitSupported = false;
+
+ changelog.commit = {
+ sha: 'unknown',
+ message: 'unknown',
+ upToDate: false
+ };
+ } else changelog.commit = {
+ sha: 'unknown',
+ message: 'unknown',
+ upToDate: false
+ };
+
+ res.json(changelog);
+});
app.get('/api/favicon', async (req, res) => {
try {
diff --git a/static/assets/js/eastereggs.js b/static/assets/js/eastereggs.js
index fd1e0a8..bfc7b90 100644
--- a/static/assets/js/eastereggs.js
+++ b/static/assets/js/eastereggs.js
@@ -287,7 +287,7 @@ easterEggs.push({
type: 'keybind',
phrase: 'rick',
run: () => new Promise((resolve, reject) => {
- const navbarTitle = document.querySelector('.title');
+ const navbarTitle = document.querySelector('.navbar>a.title');
const title = navbarTitle.querySelector('span');
title.innerHTML = 'Rick (spam click the logo)';
const logo = navbarTitle.querySelector('img');
@@ -308,6 +308,8 @@ easterEggs.push({
transform: rotate(-30deg);`;
document.body.appendChild(rick);
+ navbarTitle.dataset.action = 'no_redirect';
+
const rickClick = navbarTitle.addEventListener('click', (e) => {
e.preventDefault();
@@ -330,6 +332,7 @@ easterEggs.push({
title.innerHTML = 'Polaris by Skool';
logo.src = '/assets/img/logo.png';
+ navbarTitle.dataset.action = '';
navbarTitle.removeEventListener('click', rickClick);
rick.remove();
audio.remove();
diff --git a/static/assets/js/main.js b/static/assets/js/main.js
index 9f0fd1d..d8cd7e5 100644
--- a/static/assets/js/main.js
+++ b/static/assets/js/main.js
@@ -1,5 +1,5 @@
import loadEasterEggs from './eastereggs.js';
-import { createViewPage, isValidURL, getVH } from './utils.js';
+import { createViewPage, isValidURL, getVH, CrossTabCommunication } from './utils.js';
import PolarisError from './error.js';
import Settings from './settings.js';
import Search from './search.js';
@@ -9,6 +9,18 @@ import Apps from './apps.js';
loadEasterEggs();
+/*const ctcClient = new CrossTabCommunication();
+
+ctcClient.on('open', (connection) => {
+ connection.on('message', (message) => {
+ console.log(message);
+ });
+});
+
+setTimeout(() => {
+ ctcClient.brodcast('a');
+}, 1000);*/
+
onbeforeunload = (e) => {
document.body.style.opacity = '0.7';
@@ -26,7 +38,8 @@ onbeforeunload = (e) => {
window.addEventListener('DOMContentLoaded', () => setTimeout(() => document.body.style.opacity = 1, 1000));
document.querySelectorAll('a').forEach(hyperlink => hyperlink.addEventListener('click', (e) => {
- if (hyperlink.href && !hyperlink.target && new URL(hyperlink.href).pathname !== location.pathname) {
+ if (hyperlink.dataset.action === 'no_redirect') e.preventDefault();
+ else if (hyperlink.href && !hyperlink.target && new URL(hyperlink.href).pathname !== location.pathname) {
e.preventDefault();
document.body.style.opacity = '0.7';
diff --git a/static/assets/js/utils.js b/static/assets/js/utils.js
index 2a7a50b..3d91111 100644
--- a/static/assets/js/utils.js
+++ b/static/assets/js/utils.js
@@ -1,7 +1,7 @@
import indexedDBExporter from './utils/indexeddb.js';
+import CrossTabCommunication from './utils/ctc.js';
import EventEmitter from './utils/events.js';
import cookie from './utils/cookie.js';
-import ctc from './utils/ctc.js';
/**
* The storage interface for polaris
@@ -163,7 +163,7 @@ export default {
EventEmitter,
cookie,
uuid,
- ctc
+ CrossTabCommunication
};
export {
@@ -180,5 +180,5 @@ export {
EventEmitter,
cookie,
uuid,
- ctc
+ CrossTabCommunication
};
\ No newline at end of file
diff --git a/static/assets/js/utils/ctc.js b/static/assets/js/utils/ctc.js
index 905e342..35c537f 100644
--- a/static/assets/js/utils/ctc.js
+++ b/static/assets/js/utils/ctc.js
@@ -1,55 +1,209 @@
-import { uuid, EventEmitter } from '../utils.js';
+import { uuid } from '../utils.js';
+import EventEmitter from './events.js';
-const ctc = {
- /**
- * Check if a tab exists
- * @param {string} pageID
- * @returns {boolean}
- */
- exists: (pageID) => Object.keys(sessionStorage).includes('ctc_' + pageID),
- connect: (remoteID, currentID) => new Promise((resolve, reject) => {
- if (ctc.exists(remoteID)) {
- sessionStorage.setItem('ctc_' + remoteID, currentID);
+class CrossTabCommunication extends EventEmitter {
+ constructor() {
+ super();
- const events = new EventEmitter();
- const channel = 'ctc_connection' + currentID + '>' + remoteID;
- var prev = sessionStorage.getItem(channel);
-
- const listener = setInterval(() => {
- if (sessionStorage.getItem(channel) === 'ctc:disconnect') {
- sessionStorage.setItem(channel, '');
- events.emit('disconnect');
- clearInterval(listener);
-
- return;
- }
-
- if (prev !== sessionStorage.getItem(channel)) {
- prev = sessionStorage.getItem(channel);
+ this.registrationData = sessionStorage.getItem('ctc_registration') ? JSON.parse(sessionStorage.getItem('ctc_registration')) : {};
+ this.id = uuid();
- events.emit('message', sessionStorage.getItem(channel));
- }
- }, 10);
-
- return {
- disconnect: () => {
- clearInterval(listener);
- sessionStorage.setItem(channel, 'ctc:disconnect');
- }
- };
- } else reject('Tab does not exist');
- }),
- register: () => {
- const registrationData = sessionStorage.getItem('ctc_registration') ? JSON.parse(sessionStorage.getItem('ctc_registration')) : {};
- const pageID = uuid();
-
- registrationData[pageID] = {
+ this.registrationData[this.id] = {
location: location.href
};
- sessionStorage.setItem('ctc_' + pageID);
- }
-};
+ sessionStorage.setItem('ctc_registration', JSON.stringify(this.registrationData));
+ sessionStorage.setItem('ctc_' + this.id, 'open');
-export default ctc;
-export { ctc };
\ No newline at end of file
+ window.addEventListener('beforeunload', (e) => {
+ this.registrationData = sessionStorage.getItem('ctc_registration') ? JSON.parse(sessionStorage.getItem('ctc_registration')) : {};
+
+ if (!Object.keys(this.registrationData).length < 1) {
+ sessionStorage.setItem('ctc_registration', JSON.stringify(this.registrationData));
+ const storage = sessionStorage;
+
+ sessionStorage.clear();
+
+ for (let i = 0; i < Object.keys(storage).filter(data => !data.startsWith('ctc') && data === 'ctc_registration').length; i++) sessionStorage.setItem(Object.keys(storage)[i], storage[Object.keys(storage)[Object.keys(storage)[i]]]);
+ }
+ });
+
+ const listener = this.listen(this.id, 'public');
+
+ listener.on('message', (message) => {
+ if (message.startsWith('ctc:connection:')) {
+ const connection = this.interfaceConnection(message.replace('ctc:connection:', ''));
+
+ this.emit('open', connection);
+ }
+ });
+ }
+
+ /**
+ * Check if a tab exists
+ * @param {string} remoteID The remote client id
+ * @returns {boolean}
+ */
+ exists = (remoteID) => Object.keys(sessionStorage).includes('ctc_' + remoteID);
+
+ /**
+ * Check if a channel exists
+ * @param {string} remoteID The remote client id
+ * @param {'public' | 'private'} type The type of channel
+ * @returns {boolean}
+ */
+ channelExists = (remoteID, type) => Boolean(sessionStorage.getItem(type === 'private' ? 'ctc_connection' + this.id + '>' + remoteID : 'ctc_' + remoteID))
+
+ /**
+ * Listen for messages on a channel
+ * @param {string} remoteID The remote client id
+ * @param {'public' | 'private'} type The type of channel
+ * @returns {{ on: (event: 'message' | 'disconnect', callback: (...any) => any) => {}, once: (event: 'message' | 'disconnect', callback: (...any) => any) => {}, addEventListener: (event: 'message' | 'disconnect', callback: (...any) => any) => {}, disconnect: () => {} }}
+ */
+ listen = (remoteID, type) => {
+ if (this.channelExists(remoteID, type)) {
+ const channel = type === 'private' ? 'ctc_connection' + this.id + '>' + remoteID : 'ctc_' + remoteID;
+ var prev = sessionStorage.getItem(channel);
+ const events = new EventEmitter();
+
+ const listener = setInterval(() => {
+ if (sessionStorage.getItem(channel)) {
+ if (prev !== sessionStorage.getItem(channel)) {
+ prev = sessionStorage.getItem(channel);
+
+ events.emit('message', sessionStorage.getItem(channel));
+ }
+ } else {
+ clearInterval(listener);
+ events.emit('disconnect');
+ }
+ }, 1);
+
+ return {
+ ...events,
+ disconnect: () => {
+ clearInterval(listener);
+ events.emit('disconnect');
+ }
+ };
+ } else throw new Error('Invalid channel');
+ }
+
+ /**
+ * Connect to a tab
+ * @param {string} remoteID The remote client id
+ * @returns {{ on: (event: 'message' | 'disconnect', callback: (...any) => any) => {}, addEventListener: (event: 'message' | 'disconnect', callback: (...any) => any) => {}, once: (event: 'message' | 'disconnect', callback: (...any) => any) => {}, send: (data: string) => {}, disconnect: () => {} }}
+ */
+ connect = (remoteID) => {
+ if (this.exists(remoteID)) {
+ sessionStorage.setItem('ctc_' + remoteID, 'ctc:connection:' + this.id);
+
+ const channel = 'ctc_connection' + this.id + '>' + remoteID;
+ const listener = this.listen(remoteID, 'private');
+ const events = new EventEmitter();
+ var connected = true;
+
+ listener.on('message', (message) => {
+ if (message === 'ctc:disconnect') {
+ sessionStorage.setItem(channel, '');
+ events.emit('disconnect');
+ clearInterval(listener);
+
+ connected = false;
+
+ return;
+ }
+
+ events.emit('message', sessionStorage.getItem(channel));
+ });
+
+ listener.on('disconnect', () => {
+ events.emit('disconnect');
+
+ connected = false;
+ });
+
+ return {
+ ...events,
+ send: (data) => {
+ if (connected) sessionStorage.setItem(channel, data);
+ else throw new Error('Not connected to channel');
+ },
+ disconnect: () => {
+ if (connected) {
+ listener.disconnect();
+ sessionStorage.setItem(channel, 'ctc:disconnect');
+ } else throw new Error('Not connected to channel');
+ }
+ };
+ } else throw new Error('Invalid client');
+ }
+
+ /**
+ * Answer a connection
+ * @param {string} remoteID The remote client id
+ * @returns {{ on: (event: 'message' | 'disconnect', callback: (...any) => any) => {}, addEventListener: (event: 'message' | 'disconnect', callback: (...any) => any) => {}, once: (event: 'message' | 'disconnect', callback: (...any) => any) => {}, send: (data: string) => {}, disconnect: () => {}, pipeEvents: (EventEmitter: EventEmitter) => {} }}
+ */
+ interfaceConnection = (remoteID) => {
+ if (this.exists(remoteID)) {
+ const channel = 'ctc_connection' + remoteID + '>' + this.id;
+ const listener = this.listen(remoteID, 'private');
+ const events = new EventEmitter();
+ var connected = true;
+
+ listener.on('message', (message) => {
+ if (message === 'ctc:disconnect') {
+ sessionStorage.setItem(channel, '');
+ events.emit('disconnect');
+ clearInterval(listener);
+
+ connected = false;
+
+ return;
+ }
+
+ events.emit('message', sessionStorage.getItem(channel));
+ });
+
+ listener.on('disconnect', () => {
+ events.emit('disconnect');
+
+ connected = false;
+ });
+
+ return {
+ ...events,
+ send: (data) => {
+ if (connected) sessionStorage.setItem(channel, data);
+ else throw new Error('Not connected to channel');
+ },
+ disconnect: () => {
+ if (connected) {
+ listener.disconnect();
+ sessionStorage.setItem(channel, 'ctc:disconnect');
+ } else throw new Error('Not connected to channel');
+ },
+ /**
+ * @param {EventEmitter} EventEmitter
+ */
+ pipeEvents: (EventEmitter) => {
+ const oldEmitter = events;
+
+ events.emit = (...args) => {
+ EventEmitter.emit(...args);
+ oldEmitter.emit(...args);
+ };
+ }
+ };
+ } else throw new Error('Invalid client');
+ }
+
+ brodcast = (message) => {
+ this.registrationData = sessionStorage.getItem('ctc_registration') ? JSON.parse(sessionStorage.getItem('ctc_registration')) : {};
+
+ Object.keys(this.registrationData).forEach(remoteClient => this.connect(remoteClient).send(message));
+ }
+}
+
+export default CrossTabCommunication;
+export { CrossTabCommunication };
\ No newline at end of file