mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-08-21 03:51:49 +01:00
No player-facing changes. Ports several PRs from tgstation, including: https://github.com/tgstation/tgstation/pull/66862 https://github.com/tgstation/tgstation/pull/80189 https://github.com/tgstation/tgstation/pull/90317 https://github.com/tgstation/tgstation/pull/91379 In addition, removes IntelliCode from the recommended extensions. This is deprecated, and we prefer not to force the replacement onto contributors. Contributors may individually choose to use this instead.
309 lines
7.1 KiB
JavaScript
309 lines
7.1 KiB
JavaScript
/**
|
|
* Browser-agnostic abstraction of key-value web storage.
|
|
*
|
|
* @file
|
|
* @copyright 2020 Aleksej Komarov
|
|
* @license MIT
|
|
*/
|
|
|
|
export const IMPL_MEMORY = 0;
|
|
export const IMPL_HUB_STORAGE = 1;
|
|
export const IMPL_INDEXED_DB = 2;
|
|
export const IMPL_IFRAME_INDEXED_DB = 3;
|
|
|
|
const INDEXED_DB_VERSION = 1;
|
|
const INDEXED_DB_NAME = 'tgui';
|
|
const INDEXED_DB_STORE_NAME = 'storage-v1';
|
|
|
|
const READ_ONLY = 'readonly';
|
|
const READ_WRITE = 'readwrite';
|
|
|
|
const testGeneric = (testFn) => () => {
|
|
try {
|
|
return Boolean(testFn());
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const testHubStorage = testGeneric(
|
|
() => window.hubStorage && window.hubStorage.getItem,
|
|
);
|
|
|
|
// TODO: Remove with 516
|
|
// prettier-ignore
|
|
const testIndexedDb = testGeneric(() => (
|
|
(window.indexedDB || window.msIndexedDB)
|
|
&& (window.IDBTransaction || window.msIDBTransaction)
|
|
));
|
|
|
|
class MemoryBackend {
|
|
constructor() {
|
|
this.impl = IMPL_MEMORY;
|
|
this.store = {};
|
|
}
|
|
|
|
async get(key) {
|
|
return this.store[key];
|
|
}
|
|
|
|
async set(key, value) {
|
|
this.store[key] = value;
|
|
}
|
|
|
|
async remove(key) {
|
|
this.store[key] = undefined;
|
|
}
|
|
|
|
async clear() {
|
|
this.store = {};
|
|
}
|
|
}
|
|
|
|
class HubStorageBackend {
|
|
constructor() {
|
|
this.impl = IMPL_HUB_STORAGE;
|
|
}
|
|
|
|
async get(key) {
|
|
const value = await window.hubStorage.getItem('aurora-' + key);
|
|
if (typeof value === 'string') {
|
|
return JSON.parse(value);
|
|
}
|
|
}
|
|
|
|
async set(key, value) {
|
|
window.hubStorage.setItem('aurora-' + key, JSON.stringify(value));
|
|
}
|
|
|
|
async remove(key) {
|
|
window.hubStorage.removeItem('aurora-' + key);
|
|
}
|
|
|
|
async clear() {
|
|
window.hubStorage.clear();
|
|
}
|
|
}
|
|
|
|
class IFrameIndexedDbBackend {
|
|
constructor() {
|
|
this.impl = IMPL_IFRAME_INDEXED_DB;
|
|
}
|
|
|
|
async ready() {
|
|
const iframe = document.createElement('iframe');
|
|
iframe.style.display = 'none';
|
|
iframe.src = Byond.storageCdn;
|
|
|
|
const completePromise = new Promise((resolve) => {
|
|
iframe.onload = () => resolve(this);
|
|
});
|
|
|
|
this.documentElement = document.body.appendChild(iframe);
|
|
this.iframeWindow = this.documentElement.contentWindow;
|
|
|
|
return completePromise;
|
|
}
|
|
|
|
async get(key) {
|
|
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, value) {
|
|
this.iframeWindow.postMessage({ type: 'set', key: key, value: value }, '*');
|
|
}
|
|
|
|
async remove(key) {
|
|
this.iframeWindow.postMessage({ type: 'remove', key: key }, '*');
|
|
}
|
|
|
|
async clear() {
|
|
this.iframeWindow.postMessage({ type: 'clear' }, '*');
|
|
}
|
|
|
|
async ping() {
|
|
const promise = new Promise((resolve) => {
|
|
window.addEventListener('message', (message) => {
|
|
if (message.data === true) {
|
|
resolve(true);
|
|
}
|
|
});
|
|
|
|
setTimeout(() => resolve(false), 100);
|
|
});
|
|
|
|
this.iframeWindow.postMessage({ type: 'ping' }, '*');
|
|
return promise;
|
|
}
|
|
|
|
async processChatMessages(messages) {
|
|
this.iframeWindow.postMessage(
|
|
{ type: 'processChatMessages', messages: messages },
|
|
'*',
|
|
);
|
|
}
|
|
|
|
async getChatMessages() {
|
|
const promise = new Promise((resolve) => {
|
|
window.addEventListener('message', (message) => {
|
|
if (message.data.messages) {
|
|
resolve(message.data.messages);
|
|
}
|
|
});
|
|
});
|
|
|
|
this.iframeWindow.postMessage({ type: 'getChatMessages' }, '*');
|
|
return promise;
|
|
}
|
|
|
|
async setNumberStored(number) {
|
|
this.iframeWindow.postMessage(
|
|
{ type: 'setNumberStored', newMax: number },
|
|
'*',
|
|
);
|
|
}
|
|
|
|
async destroy() {
|
|
document.body.removeChild(this.documentElement);
|
|
this.documentElement = null;
|
|
this.iframeWindow = null;
|
|
}
|
|
}
|
|
|
|
class IndexedDbBackend {
|
|
// TODO: Remove with 516
|
|
constructor() {
|
|
this.impl = IMPL_INDEXED_DB;
|
|
/** @type {Promise<IDBDatabase>} */
|
|
this.dbPromise = new Promise((resolve, reject) => {
|
|
const indexedDB = window.indexedDB || window.msIndexedDB;
|
|
const req = indexedDB.open(INDEXED_DB_NAME, INDEXED_DB_VERSION);
|
|
req.onupgradeneeded = () => {
|
|
try {
|
|
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));
|
|
};
|
|
});
|
|
}
|
|
|
|
async getStore(mode) {
|
|
// prettier-ignore
|
|
return this.dbPromise.then((db) => db
|
|
.transaction(INDEXED_DB_STORE_NAME, mode)
|
|
.objectStore(INDEXED_DB_STORE_NAME));
|
|
}
|
|
|
|
async get(key) {
|
|
const store = await this.getStore(READ_ONLY);
|
|
return new Promise((resolve, reject) => {
|
|
const req = store.get(key);
|
|
req.onsuccess = () => resolve(req.result);
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
|
|
async set(key, value) {
|
|
// NOTE: We deliberately make this operation transactionless
|
|
const store = await this.getStore(READ_WRITE);
|
|
store.put(value, key);
|
|
}
|
|
|
|
async remove(key) {
|
|
// NOTE: We deliberately make this operation transactionless
|
|
const store = await this.getStore(READ_WRITE);
|
|
store.delete(key);
|
|
}
|
|
|
|
async clear() {
|
|
// NOTE: We deliberately make this operation transactionless
|
|
const store = await this.getStore(READ_WRITE);
|
|
store.clear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Web Storage Proxy object, which selects the best backend available
|
|
* depending on the environment.
|
|
*/
|
|
export class StorageProxy {
|
|
constructor() {
|
|
this.backendPromise = (async () => {
|
|
if (!Byond.TRIDENT) {
|
|
if (Byond.storageCdn) {
|
|
const iframe = new IFrameIndexedDbBackend();
|
|
await iframe.ready();
|
|
|
|
if ((await iframe.ping()) === true) {
|
|
return iframe;
|
|
}
|
|
|
|
iframe.destroy();
|
|
}
|
|
|
|
if (!testHubStorage()) {
|
|
Byond.winset(null, 'browser-options', '+byondstorage');
|
|
|
|
return new Promise((resolve) => {
|
|
const listener = () => {
|
|
document.removeEventListener('byondstorageupdated', listener);
|
|
resolve(new HubStorageBackend());
|
|
};
|
|
|
|
document.addEventListener('byondstorageupdated', listener);
|
|
});
|
|
}
|
|
return new HubStorageBackend();
|
|
}
|
|
// TODO: Remove with 516
|
|
if (testIndexedDb()) {
|
|
try {
|
|
const backend = new IndexedDbBackend();
|
|
await backend.dbPromise;
|
|
return backend;
|
|
} catch {}
|
|
}
|
|
console.warn(
|
|
'No supported storage backend found. Using in-memory storage.',
|
|
);
|
|
return new MemoryBackend();
|
|
})();
|
|
}
|
|
|
|
async get(key) {
|
|
const backend = await this.backendPromise;
|
|
return backend.get(key);
|
|
}
|
|
|
|
async set(key, value) {
|
|
const backend = await this.backendPromise;
|
|
return backend.set(key, value);
|
|
}
|
|
|
|
async remove(key) {
|
|
const backend = await this.backendPromise;
|
|
return backend.remove(key);
|
|
}
|
|
|
|
async clear() {
|
|
const backend = await this.backendPromise;
|
|
return backend.clear();
|
|
}
|
|
}
|
|
|
|
export const storage = new StorageProxy();
|