mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-09 07:57:00 +00:00
Compare commits
7 Commits
1b99ac2969
...
ad7d82f3ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad7d82f3ae | ||
|
|
c4337dc60a | ||
|
|
90bebd219a | ||
|
|
067ab7459b | ||
|
|
a42bdce47b | ||
|
|
28a8d23bf6 | ||
|
|
ba5019bd9d |
33
.github/workflows/generate_client_storage.yml
vendored
Normal file
33
.github/workflows/generate_client_storage.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: "Generate Client Storage"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- tgui/public/*
|
||||
|
||||
jobs:
|
||||
dispatch_repo:
|
||||
if: ( !contains(github.event.head_commit.message, '[ci skip]') )
|
||||
name: Repository Dispatch
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Generate App Token
|
||||
id: app-token-generation
|
||||
uses: actions/create-github-app-token@v2
|
||||
if: env.APP_PRIVATE_KEY != '' && env.APP_ID != ''
|
||||
with:
|
||||
app-id: ${{ secrets.APP_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
owner: vorestation
|
||||
env:
|
||||
APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
APP_ID: ${{ secrets.APP_ID }}
|
||||
|
||||
- name: Send Repository Dispatch
|
||||
if: success()
|
||||
uses: peter-evans/repository-dispatch@v4
|
||||
with:
|
||||
token: ${{ steps.app-token-generation.outputs.token }}
|
||||
repository: vorestation/byond-client-storage
|
||||
event-type: on_master_push
|
||||
@@ -58,4 +58,4 @@
|
||||
#endif //ifdef REFERENCE_TRACKING
|
||||
|
||||
// Standard flags to use for browser-options
|
||||
#define DEFAULT_CLIENT_BROWSER_OPTIONS "byondstorage,find"
|
||||
#define DEFAULT_CLIENT_BROWSER_OPTIONS "find"
|
||||
|
||||
@@ -11,3 +11,7 @@
|
||||
/datum/config_entry/flag/smart_cache_assets
|
||||
|
||||
/datum/config_entry/flag/save_spritesheets
|
||||
|
||||
/datum/config_entry/string/storage_cdn_iframe
|
||||
protection = CONFIG_ENTRY_LOCKED
|
||||
default = "https://vorestation.github.io/byond-client-storage/iframe.html"
|
||||
|
||||
@@ -15,9 +15,12 @@ SUBSYSTEM_DEF(tgui)
|
||||
wait = 9
|
||||
flags = SS_NO_INIT
|
||||
priority = FIRE_PRIORITY_TGUI
|
||||
init_stage = INITSTAGE_EARLY
|
||||
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
|
||||
|
||||
dependencies = list(
|
||||
/datum/controller/subsystem/assets
|
||||
)
|
||||
|
||||
/// A list of UIs scheduled to process
|
||||
var/list/current_run = list()
|
||||
/// A list of all open UIs
|
||||
@@ -40,6 +43,25 @@ SUBSYSTEM_DEF(tgui)
|
||||
|
||||
basehtml = replacetextEx(basehtml, "<!-- tgui:nt-copyright -->", "Nanotrasen (c) 2284-[text2num(time2text(world.realtime,"YYYY")) + STATION_YEAR_OFFSET]")
|
||||
|
||||
/datum/controller/subsystem/tgui/OnConfigLoad()
|
||||
var/storage_iframe = CONFIG_GET(string/storage_cdn_iframe)
|
||||
|
||||
if(storage_iframe && storage_iframe != /datum/config_entry/string/storage_cdn_iframe::default)
|
||||
basehtml = replacetextEx(basehtml, "\[tgui:storagecdn]", storage_iframe)
|
||||
return
|
||||
|
||||
if(CONFIG_GET(string/asset_transport) == "webroot")
|
||||
var/datum/asset_transport/webroot/webroot = SSassets.transport
|
||||
|
||||
var/datum/asset_cache_item/item = webroot.register_asset("iframe.html", file("tgui/public/iframe.html"))
|
||||
basehtml = replacetextEx(basehtml, "\[tgui:storagecdn]", webroot.get_asset_url("iframe.html", item))
|
||||
return
|
||||
|
||||
if(!storage_iframe)
|
||||
return
|
||||
|
||||
basehtml = replacetextEx(basehtml, "\[tgui:storagecdn]", storage_iframe)
|
||||
|
||||
/datum/controller/subsystem/tgui/Shutdown()
|
||||
close_all_uis()
|
||||
|
||||
|
||||
@@ -782,3 +782,8 @@ ADMIN_VERB(quick_nif, R_ADMIN, "Quick NIF", "Spawns a NIF into someone in quick-
|
||||
|
||||
log_and_message_admins("Quick NIF'd [H.real_name] with a [input_NIF].", user)
|
||||
feedback_add_details("admin_verb","QNIF") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
ADMIN_VERB(reload_configuration, R_DEBUG, "Reload Configuration", "Reloads the configuration from the default path on the disk, wiping any in-round modifications.", ADMIN_CATEGORY_DEBUG)
|
||||
if(tgui_alert(user, "Are you absolutely sure you want to reload the configuration from the default path on the disk, wiping any in-round modifications?", "Really reset?", list("No", "Yes")) != "Yes")
|
||||
return
|
||||
config.admin_reload()
|
||||
|
||||
@@ -269,6 +269,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
if (CONFIG_GET(flag/chatlog_database_backend))
|
||||
chatlog_token = vchatlog_generate_token(ckey, GLOB.round_id)
|
||||
|
||||
winset(src, null, list("browser-options" = "find,refresh"))
|
||||
// Instantiate stat panel
|
||||
stat_panel = new(src, "statbrowser")
|
||||
stat_panel.subscribe(src, PROC_REF(on_stat_panel_message))
|
||||
|
||||
@@ -42,3 +42,12 @@ SMART_CACHE_ASSETS
|
||||
## Useful for developers to debug potential spritesheet issues to determine where the issue is cropping up (either in DM-side sprite generation or in the TGUI-side display of said spritesheet).
|
||||
## Will only seek to waste disk space if ran on production.
|
||||
#SAVE_SPRITESHEETS
|
||||
|
||||
# If configured, this allows server operators to define the persistent origin used for clientside
|
||||
# storage. This must host the same file as available in tgui/public/iframe.html. This is also hosted
|
||||
# on the GitHub Pages site for the /tg/station repository, so does not need to be configured.
|
||||
# If multiple servers use the same domain name, clientside features such as message saving
|
||||
# and chat tabs will be persistent across both.
|
||||
# If this setting is not configured, but the webroot CDN is, that will be used instead of GitHub Pages.
|
||||
# If this setting is mpty, and the webroot CDN is disabled, byondstorage will be used.
|
||||
# STORAGE_CDN_IFRAME https://vorestation.github.io/byond-client-storage/iframe.html
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
author: "CHOMPStation2StaffMirrorBot"
|
||||
delete-after: True
|
||||
changes:
|
||||
- bugfix: "sun subsystem overruns"
|
||||
- bugfix: "nightshift subsystem overruns"
|
||||
@@ -1,4 +0,0 @@
|
||||
author: "Will"
|
||||
delete-after: True
|
||||
changes:
|
||||
- bugfix: "Phased shadekin are no longer knocked down or thrown when a shuttle they are inside of moves."
|
||||
@@ -31,3 +31,14 @@
|
||||
FluffMedic:
|
||||
- bugfix: A certain Tyr boss mechanic functions as intended, and one tyr wrecked
|
||||
shuttles is more accurate
|
||||
2025-12-05:
|
||||
CHOMPStation2StaffMirrorBot:
|
||||
- bugfix: windows should no longer be just white from an invalid iframe
|
||||
- admin: re-added the 'reload configuration' verb to reload config from disk
|
||||
- bugfix: fixed the hairs I tried to add a bit ago
|
||||
- bugfix: sun subsystem overruns
|
||||
- bugfix: nightshift subsystem overruns
|
||||
- bugfix: some remaining hitching issues
|
||||
Will:
|
||||
- bugfix: Phased shadekin are no longer knocked down or thrown when a shuttle they
|
||||
are inside of moves.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 204 KiB |
5
tgui/global.d.ts
vendored
5
tgui/global.d.ts
vendored
@@ -63,6 +63,11 @@ type ByondType = {
|
||||
*/
|
||||
strictMode: boolean;
|
||||
|
||||
/**
|
||||
* The external URL for the IndexedDB IFrame to use as the origin
|
||||
*/
|
||||
storageCdn: string;
|
||||
|
||||
/**
|
||||
* Makes a BYOND call.
|
||||
*
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export const IMPL_MEMORY = 0;
|
||||
export const IMPL_HUB_STORAGE = 1;
|
||||
|
||||
type StorageImplementation = typeof IMPL_MEMORY | typeof IMPL_HUB_STORAGE;
|
||||
export const IMPL_IFRAME_INDEXED_DB = 2;
|
||||
|
||||
const KEY_NAME = 'chomp'; // CHOMPEdit - CHOMPStation Localstore
|
||||
type StorageImplementation =
|
||||
| typeof IMPL_HUB_STORAGE
|
||||
| typeof IMPL_IFRAME_INDEXED_DB;
|
||||
|
||||
type StorageBackend = {
|
||||
impl: StorageImplementation;
|
||||
@@ -61,28 +62,154 @@ class HubStorageBackend implements StorageBackend {
|
||||
}
|
||||
}
|
||||
|
||||
class IFrameIndexedDbBackend implements StorageBackend {
|
||||
public impl: StorageImplementation;
|
||||
|
||||
private documentElement: HTMLIFrameElement;
|
||||
private iframeWindow: Window;
|
||||
|
||||
constructor() {
|
||||
this.impl = IMPL_IFRAME_INDEXED_DB;
|
||||
}
|
||||
|
||||
async ready(): Promise<boolean | null> {
|
||||
const iframe = document.createElement('iframe');
|
||||
const iframeStore = `${Byond.storageCdn}?store=${KEY_NAME}`;
|
||||
iframe.style.display = 'none';
|
||||
this.documentElement = document.body.appendChild(iframe);
|
||||
iframe.src = iframeStore;
|
||||
|
||||
const completePromise: Promise<boolean> = new Promise((resolve) => {
|
||||
fetch(iframeStore, { method: 'HEAD' })
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
resolve(false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
window.addEventListener('message', (message) => {
|
||||
if (message.data === 'ready') {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!this.documentElement.contentWindow) {
|
||||
return new Promise((res) => res(false));
|
||||
}
|
||||
|
||||
this.iframeWindow = this.documentElement.contentWindow;
|
||||
|
||||
return completePromise;
|
||||
}
|
||||
|
||||
async get(key: string): Promise<any> {
|
||||
const promise = new Promise((resolve) => {
|
||||
window.addEventListener('message', (message) => {
|
||||
if (message.data.key && message.data.key === key) {
|
||||
resolve(message.data.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.iframeWindow.postMessage({ type: 'get', key: key }, '*');
|
||||
return promise;
|
||||
}
|
||||
|
||||
async set(key: string, value: any): Promise<void> {
|
||||
this.iframeWindow.postMessage({ type: 'set', key: key, value: value }, '*');
|
||||
}
|
||||
|
||||
async remove(key: string): Promise<void> {
|
||||
this.iframeWindow.postMessage({ type: 'remove', key: key }, '*');
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
this.iframeWindow.postMessage({ type: 'clear' }, '*');
|
||||
}
|
||||
|
||||
async destroy(): Promise<void> {
|
||||
document.body.removeChild(this.documentElement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Web Storage Proxy object, which selects the best backend available
|
||||
* depending on the environment.
|
||||
*/
|
||||
class StorageProxy implements StorageBackend {
|
||||
private backendPromise: Promise<StorageBackend>;
|
||||
public impl: StorageImplementation = IMPL_MEMORY;
|
||||
public impl: StorageImplementation = IMPL_IFRAME_INDEXED_DB;
|
||||
|
||||
constructor() {
|
||||
this.backendPromise = (async () => {
|
||||
// If we have not enabled byondstorage yet, we need to check
|
||||
// if we can use the IFrame, or if we need to enable byondstorage
|
||||
console.log(`testHubStorage ${testHubStorage()}`);
|
||||
if (!testHubStorage()) {
|
||||
// If we have an IFrame URL we can use, and we haven't already enabled
|
||||
// byondstorage, we should use the IFrame backend
|
||||
console.log(`storageCdn: ${Byond.storageCdn}`);
|
||||
if (Byond.storageCdn) {
|
||||
const iframe = new IFrameIndexedDbBackend();
|
||||
|
||||
if ((await iframe.ready()) === true) {
|
||||
if (await iframe.get('byondstorage-migrated')) return iframe;
|
||||
|
||||
Byond.winset(null, 'browser-options', '+byondstorage');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
document.addEventListener('byondstorageupdated', async () => {
|
||||
setTimeout(() => {
|
||||
const hub = new HubStorageBackend();
|
||||
|
||||
// Migrate these existing settings from byondstorage to the IFrame
|
||||
for (const setting of [
|
||||
'panel-settings',
|
||||
'chat-state',
|
||||
'chat-messages',
|
||||
]) {
|
||||
hub
|
||||
.get(setting)
|
||||
.then((settings) => iframe.set(setting, settings));
|
||||
}
|
||||
|
||||
iframe.set('byondstorage-migrated', true);
|
||||
Byond.winset(null, 'browser-options', '-byondstorage');
|
||||
|
||||
resolve();
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
|
||||
return iframe;
|
||||
}
|
||||
|
||||
iframe.destroy();
|
||||
}
|
||||
|
||||
// IFrame hasn't worked out for us, we'll need to enable byondstorage
|
||||
Byond.winset(null, 'browser-options', '+byondstorage');
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const listener = () => {
|
||||
document.removeEventListener('byondstorageupdated', listener);
|
||||
resolve(new HubStorageBackend());
|
||||
|
||||
// This event is emitted *before* byondstorage is actually created
|
||||
// so we have to wait a little bit before we can use it
|
||||
setTimeout(() => resolve(new HubStorageBackend()), 1);
|
||||
};
|
||||
|
||||
document.addEventListener('byondstorageupdated', listener);
|
||||
});
|
||||
}
|
||||
|
||||
// byondstorage is already enabled, we can use it straight away
|
||||
return new HubStorageBackend();
|
||||
})() as Promise<StorageBackend>;
|
||||
})();
|
||||
}
|
||||
|
||||
async get(key: string): Promise<any> {
|
||||
|
||||
@@ -334,6 +334,7 @@ export const chatMiddleware = (store) => {
|
||||
next(action);
|
||||
const page = selectCurrentChatPage(store.getState());
|
||||
chatRenderer.changePage(page);
|
||||
needsUpdate = true;
|
||||
return;
|
||||
}
|
||||
if (type === rebuildChat.type) {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
// Expose inlined metadata
|
||||
Byond.windowId = parseMetaTag('tgui:windowId');
|
||||
Byond.storageCdn = parseMetaTag('tgui:storagecdn');
|
||||
|
||||
// Backwards compatibility
|
||||
window.__windowId__ = Byond.windowId;
|
||||
@@ -397,32 +398,17 @@
|
||||
],
|
||||
};
|
||||
|
||||
try {
|
||||
window
|
||||
.showSaveFilePicker(opts)
|
||||
.then((fileHandle) => {
|
||||
fileHandle
|
||||
.createWritable()
|
||||
.then((writeableFileHandle) => {
|
||||
writeableFileHandle
|
||||
.write(blob)
|
||||
.then(() => {
|
||||
writeableFileHandle.close();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
window
|
||||
.showSaveFilePicker(opts)
|
||||
.then(function (file) {
|
||||
return file.createWritable();
|
||||
})
|
||||
.then(function (file) {
|
||||
return file.write(blob).then(function () {
|
||||
return file.close();
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
})
|
||||
.catch(function () {});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
2
tgui/public/helpers.min.js
vendored
2
tgui/public/helpers.min.js
vendored
File diff suppressed because one or more lines are too long
105
tgui/public/iframe.html
Normal file
105
tgui/public/iframe.html
Normal file
@@ -0,0 +1,105 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<script type="text/javascript">
|
||||
const INDEXED_DB_VERSION = 1;
|
||||
const INDEXED_DB_NAME = 'tgui';
|
||||
const INDEXED_DB_STORE_NAME = 'storage';
|
||||
|
||||
const READ_ONLY = 'readonly';
|
||||
const READ_WRITE = 'readwrite';
|
||||
|
||||
const MAX_MESSAGES = 1000;
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const storeValue = `${urlParams.get('store')}-${INDEXED_DB_NAME}`;
|
||||
|
||||
const dbPromise = new Promise((resolve, reject) => {
|
||||
const indexedDB = window.indexedDB;
|
||||
const req = indexedDB.open(storeValue, INDEXED_DB_VERSION);
|
||||
req.onupgradeneeded = (event) => {
|
||||
try {
|
||||
if (event.oldVersion < 1) {
|
||||
req.result.createObjectStore(INDEXED_DB_STORE_NAME);
|
||||
}
|
||||
} catch (err) {
|
||||
reject(new Error('Failed to upgrade IDB: ' + req.error));
|
||||
}
|
||||
};
|
||||
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
|
||||
req.onerror = () => {
|
||||
reject(new Error('Failed to open IDB: ' + req.error));
|
||||
};
|
||||
});
|
||||
|
||||
window.addEventListener('message', (messageEvent) => {
|
||||
switch (messageEvent.data.type) {
|
||||
case 'ping':
|
||||
messageEvent.source.postMessage(true, '*');
|
||||
break;
|
||||
case 'get':
|
||||
get(messageEvent.data.key).then((value) => {
|
||||
messageEvent.source.postMessage(
|
||||
{ key: messageEvent.data.key, value: value },
|
||||
'*',
|
||||
);
|
||||
});
|
||||
break;
|
||||
case 'set':
|
||||
set(messageEvent.data.key, messageEvent.data.value);
|
||||
break;
|
||||
case 'remove':
|
||||
remove(messageEvent.data.key);
|
||||
break;
|
||||
case 'clear':
|
||||
clear();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const getStore = async (mode) => {
|
||||
return dbPromise.then((db) =>
|
||||
db
|
||||
.transaction(INDEXED_DB_STORE_NAME, mode)
|
||||
.objectStore(INDEXED_DB_STORE_NAME),
|
||||
);
|
||||
};
|
||||
|
||||
const get = async (key) => {
|
||||
const store = await getStore(READ_ONLY);
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = store.get(key);
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
};
|
||||
|
||||
const set = async (key, value) => {
|
||||
const store = await getStore(READ_WRITE);
|
||||
store.put(value, key);
|
||||
};
|
||||
|
||||
const remove = async (key) => {
|
||||
const store = await getStore(READ_WRITE);
|
||||
store.delete(key);
|
||||
};
|
||||
|
||||
const clear = async () => {
|
||||
const store = await getStore(READ_WRITE);
|
||||
store.clear();
|
||||
};
|
||||
|
||||
window.onload = () => {
|
||||
window.parent.postMessage('ready', '*');
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
||||
@@ -6,6 +6,7 @@
|
||||
<!-- Inlined metadata -->
|
||||
<meta id="tgui:windowId" content="[tgui:windowId]" />
|
||||
<meta id="tgui:strictMode" content="[tgui:strictMode]" />
|
||||
<meta id="tgui:storagecdn" content="[tgui:storagecdn]" />
|
||||
|
||||
<!-- Early setup -->
|
||||
<!-- tgui:helpers -->
|
||||
|
||||
Reference in New Issue
Block a user