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