Added async version of EEPROM script.

This commit is contained in:
flash 2024-02-02 21:00:24 +00:00
parent b22f6df752
commit 10937f1217
12 changed files with 990 additions and 214 deletions

2
.gitignore vendored
View file

@ -3,9 +3,11 @@
.DS_Store
/public/data/*
/public/thumb/*
/public/scripts
/public/robots.txt
/config.cfg
/config.ini
/.debug
/vendor
/.migrating
/node_modules

102
build.js Normal file
View file

@ -0,0 +1,102 @@
// IMPORTS
const fs = require('fs');
const swc = require('@swc/core');
const path = require('path');
const utils = require('./scripts/utils.js');
const assproc = require('./scripts/assproc.js');
// CONFIG
const rootDir = __dirname;
const srcDir = path.join(rootDir, 'scripts');
const pubDir = path.join(rootDir, 'public');
const pubAssetsDir = path.join(pubDir, 'scripts');
const isDebugBuild = fs.existsSync(path.join(rootDir, '.debug'));
const buildTasks = {
js: [
{ source: 'eepromv1.js', target: '/scripts', name: 'eepromv1.js', es: 'es5' },
{ source: 'eepromv1a.js', target: '/scripts', name: 'eepromv1a.js' },
],
};
// PREP
const swcJscOptions = {
target: 'es2021',
loose: false,
externalHelpers: false,
keepClassNames: true,
preserveAllComments: false,
transform: {},
parser: {
syntax: 'ecmascript',
jsx: false,
dynamicImport: false,
privateMethod: false,
functionBind: false,
exportDefaultFrom: false,
exportNamespaceFrom: false,
decorators: false,
decoratorsBeforeExport: false,
topLevelAwait: true,
importMeta: false,
},
};
// BUILD
(async () => {
const files = {};
console.log('Ensuring assets directory exists...');
fs.mkdirSync(pubAssetsDir, { recursive: true });
console.log();
console.log('JS assets');
for(const info of buildTasks.js) {
console.log(`=> Building ${info.source}...`);
let origTarget = undefined;
if('es' in info) {
origTarget = swcJscOptions.target;
swcJscOptions.target = info.es;
}
const assprocOpts = {
prefix: '#',
entry: info.entry || 'main.js',
};
const swcOpts = {
filename: info.source,
sourceMaps: false,
isModule: false,
minify: !isDebugBuild,
jsc: swcJscOptions,
};
const pubName = await assproc.process(path.join(srcDir, info.source), assprocOpts)
.then(output => swc.transform(output, swcOpts))
.then(output => {
const name = utils.strtr(info.name, { hash: utils.shortHash(output.code) });
const pubName = path.join(info.target || '', name);
console.log(` Saving to ${pubName}...`);
fs.writeFileSync(path.join(pubDir, pubName), output.code);
return pubName;
});
if(origTarget !== undefined)
swcJscOptions.target = origTarget;
files[info.source] = pubName;
}
console.log();
console.log('Cleaning up old builds...');
assproc.housekeep(pubAssetsDir);
})();

209
package-lock.json generated Normal file
View file

@ -0,0 +1,209 @@
{
"name": "eeprom.edgii.net",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@swc/core": "^1.3.107"
}
},
"node_modules/@swc/core": {
"version": "1.3.107",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.107.tgz",
"integrity": "sha512-zKhqDyFcTsyLIYK1iEmavljZnf4CCor5pF52UzLAz4B6Nu/4GLU+2LQVAf+oRHjusG39PTPjd2AlRT3f3QWfsQ==",
"hasInstallScript": true,
"dependencies": {
"@swc/counter": "^0.1.1",
"@swc/types": "^0.1.5"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.3.107",
"@swc/core-darwin-x64": "1.3.107",
"@swc/core-linux-arm-gnueabihf": "1.3.107",
"@swc/core-linux-arm64-gnu": "1.3.107",
"@swc/core-linux-arm64-musl": "1.3.107",
"@swc/core-linux-x64-gnu": "1.3.107",
"@swc/core-linux-x64-musl": "1.3.107",
"@swc/core-win32-arm64-msvc": "1.3.107",
"@swc/core-win32-ia32-msvc": "1.3.107",
"@swc/core-win32-x64-msvc": "1.3.107"
},
"peerDependencies": {
"@swc/helpers": "^0.5.0"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.3.107",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.107.tgz",
"integrity": "sha512-47tD/5vSXWxPd0j/ZllyQUg4bqalbQTsmqSw0J4dDdS82MWqCAwUErUrAZPRjBkjNQ6Kmrf5rpCWaGTtPw+ngw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.3.107",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.107.tgz",
"integrity": "sha512-hwiLJ2ulNkBGAh1m1eTfeY1417OAYbRGcb/iGsJ+LuVLvKAhU/itzsl535CvcwAlt2LayeCFfcI8gdeOLeZa9A==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.3.107",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.107.tgz",
"integrity": "sha512-I2wzcC0KXqh0OwymCmYwNRgZ9nxX7DWnOOStJXV3pS0uB83TXAkmqd7wvMBuIl9qu4Hfomi9aDM7IlEEn9tumQ==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.3.107",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.107.tgz",
"integrity": "sha512-HWgnn7JORYlOYnGsdunpSF8A+BCZKPLzLtEUA27/M/ZuANcMZabKL9Zurt7XQXq888uJFAt98Gy+59PU90aHKg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.3.107",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.107.tgz",
"integrity": "sha512-vfPF74cWfAm8hyhS8yvYI94ucMHIo8xIYU+oFOW9uvDlGQRgnUf/6DEVbLyt/3yfX5723Ln57U8uiMALbX5Pyw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.3.107",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.107.tgz",
"integrity": "sha512-uBVNhIg0ip8rH9OnOsCARUFZ3Mq3tbPHxtmWk9uAa5u8jQwGWeBx5+nTHpDOVd3YxKb6+5xDEI/edeeLpha/9g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.3.107",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.107.tgz",
"integrity": "sha512-mvACkUvzSIB12q1H5JtabWATbk3AG+pQgXEN95AmEX2ZA5gbP9+B+mijsg7Sd/3tboHr7ZHLz/q3SHTvdFJrEw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.3.107",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.107.tgz",
"integrity": "sha512-J3P14Ngy/1qtapzbguEH41kY109t6DFxfbK4Ntz9dOWNuVY3o9/RTB841ctnJk0ZHEG+BjfCJjsD2n8H5HcaOA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.3.107",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.107.tgz",
"integrity": "sha512-ZBUtgyjTHlz8TPJh7kfwwwFma+ktr6OccB1oXC8fMSopD0AxVnQasgun3l3099wIsAB9eEsJDQ/3lDkOLs1gBA==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.3.107",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.107.tgz",
"integrity": "sha512-Eyzo2XRqWOxqhE1gk9h7LWmUf4Bp4Xn2Ttb0ayAXFp6YSTxQIThXcT9kipXZqcpxcmDwoq8iWbbf2P8XL743EA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/counter": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz",
"integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw=="
},
"node_modules/@swc/types": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz",
"integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw=="
}
}
}

5
package.json Normal file
View file

@ -0,0 +1,5 @@
{
"dependencies": {
"@swc/core": "^1.3.107"
}
}

View file

@ -1,213 +0,0 @@
var EEPROM = function(srcId, endpoint, authorization) {
var obj = {
srcId: parseInt(srcId),
endpoint: endpoint,
authorization: authorization,
};
obj.setEndpoint = function(endpoint) {
obj.endpoint = (endpoint || '').toString();
};
obj.setAuthorization = function(authorization) {
obj.authorization = (authorization || '').toString();
};
obj.deleteUpload = function(fileInfo) {
return new EEPROM.EEPROMDeleteTask(
obj.authorization,
fileInfo
);
};
obj.createUpload = function(file) {
return new EEPROM.EEPROMUploadTask(
obj.srcId,
obj.endpoint,
obj.authorization,
file
);
};
return obj;
};
EEPROM.ERR_GENERIC = 'generic';
EEPROM.ERR_INVALID = 'invalid';
EEPROM.ERR_AUTH = 'auth';
EEPROM.ERR_ACCESS = 'access';
EEPROM.ERR_DMCA = 'dmca';
EEPROM.ERR_GONE = 'gone';
EEPROM.ERR_SERVER = 'server';
EEPROM.ERR_SIZE = 'size';
EEPROM.EEPROMFile = function(fileInfo) {
var obj = {
id: (fileInfo.id || '').toString(),
url: (fileInfo.url || '').toString(),
urlf: (fileInfo.urlf || '').toString(),
thumb: (fileInfo.thumb || '').toString(),
name: (fileInfo.name || '').toString(),
type: (fileInfo.type || '').toString(),
size: parseInt(fileInfo.size || 0),
user: parseInt(fileInfo.user || 0),
hash: (fileInfo.hash || '').toString(),
created: (fileInfo.created || null),
accessed: (fileInfo.accessed || null),
expires: (fileInfo.expires || null),
deleted: (fileInfo.deleted || null),
dmca: (fileInfo.dmca || null),
};
obj.isImage = function() { return obj.type.indexOf('image/') === 0; };
obj.isAudio = function() { return obj.type === 'application/x-font-gdos' || obj.type.indexOf('audio/') === 0; };
obj.isVideo = function() { return obj.type.indexOf('video/') === 0; };
obj.isMedia = function() { return obj.isImage() || obj.isAudio() || obj.isVideo(); };
return obj;
};
EEPROM.EEPROMDeleteTask = function(authorization, fileInfo) {
var obj = {
authorization: authorization,
fileInfo: fileInfo,
onSuccess: undefined,
onFailure: undefined,
};
var xhr = obj.xhr = new XMLHttpRequest;
obj.xhr.addEventListener('readystatechange', function() {
if(xhr.readyState !== 4)
return;
if(xhr.status !== 204) {
obj.errorCode = EEPROM.ERR_GENERIC;
switch(xhr.status) {
case 401:
obj.errorCode = EEPROM.ERR_AUTH;
break;
case 403:
obj.errorCode = EEPROM.ERR_ACCESS;
break;
case 404:
case 410:
obj.errorCode = EEPROM.ERR_GONE;
break;
case 500:
case 503:
obj.errorCode = EEPROM.ERR_SERVER;
break;
}
if(obj.onFailure)
obj.onFailure(obj.errorCode);
return;
}
if(obj.onSuccess)
obj.onSuccess();
});
obj.start = function() {
xhr.open('DELETE', obj.fileInfo.urlf);
if(obj.authorization) xhr.setRequestHeader('Authorization', obj.authorization);
else xhr.withCredentials = true;
xhr.send();
};
return obj;
};
EEPROM.EEPROMUploadTask = function(srcId, endpoint, authorization, file) {
var obj = {
aborted: false,
endpoint: endpoint,
authorization: authorization,
onComplete: undefined,
onFailure: undefined,
onProgress: undefined,
failureResponse: undefined,
};
var xhr = obj.xhr = new XMLHttpRequest,
fd = obj.formData = new FormData;
fd.append('src', srcId);
fd.append('file', file);
var reportUploadProgress = function(ev) {
if(obj.onProgress)
obj.onProgress({
loaded: ev.loaded,
total: ev.total,
progress: Math.ceil((ev.loaded / ev.total) * 100),
});
};
xhr.upload.addEventListener('loadstart', reportUploadProgress);
xhr.upload.addEventListener('progress', reportUploadProgress);
xhr.upload.addEventListener('load', reportUploadProgress);
xhr.addEventListener('readystatechange', function() {
if(this.readyState !== 4)
return;
if(this.status !== 201) {
obj.failureResponse = {
userAborted: obj.aborted,
error: EEPROM.ERR_GENERIC,
};
switch(this.status) {
case 400:
case 405:
obj.failureResponse.error = EEPROM.ERR_INVALID;
break;
case 401:
obj.failureResponse.error = EEPROM.ERR_AUTH;
break;
case 403:
obj.failureResponse.error = EEPROM.ERR_ACCESS;
break;
case 404:
case 410:
obj.failureResponse.error = EEPROM.ERR_GONE;
break;
case 451:
obj.failureResponse.error = EEPROM.ERR_DMCA;
break;
case 413:
obj.failureResponse.error = EEPROM.ERR_SIZE;
obj.failureResponse.maxSize = parseInt(this.getResponseHeader('X-EEPROM-Max-Size'));
break;
case 500:
case 503:
obj.failureResponse.error = EEPROM.ERR_SERVER;
break;
}
if(obj.onFailure)
obj.onFailure(obj.failureResponse);
return;
}
obj.fileInfo = new EEPROM.EEPROMFile(JSON.parse(this.responseText));
if(obj.onComplete)
obj.onComplete(obj.fileInfo);
});
obj.abort = function() {
obj.aborted = true;
xhr.abort();
};
obj.start = function() {
xhr.open('POST', obj.endpoint);
if(obj.authorization) xhr.setRequestHeader('Authorization', obj.authorization);
else xhr.withCredentials = true;
xhr.send(fd);
};
return obj;
};

108
scripts/assproc.js Normal file
View file

@ -0,0 +1,108 @@
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const utils = require('./utils.js');
exports.process = async function(root, options) {
const macroPrefix = options.prefix || '#';
const entryPoint = options.entry || '';
root = fs.realpathSync(root);
const included = [];
const processFile = async function(fileName) {
const fullPath = path.join(root, fileName);
if(included.includes(fullPath))
return '';
included.push(fullPath);
if(!fullPath.startsWith(root))
return '/* *** INVALID PATH: ' + fullPath + ' */';
if(!fs.existsSync(fullPath))
return '/* *** FILE NOT FOUND: ' + fullPath + ' */';
const lines = readline.createInterface({
input: fs.createReadStream(fullPath),
crlfDelay: Infinity,
});
let output = '';
let lastWasEmpty = false;
if(options.showPath)
output += "/* *** PATH: " + fullPath + " */\n";
for await(const line of lines) {
const lineTrimmed = utils.trim(line);
if(lineTrimmed === '')
continue;
if(line.startsWith(macroPrefix)) {
const args = lineTrimmed.split(' ');
const macro = utils.trim(utils.trimStart(args.shift(), macroPrefix));
switch(macro) {
case 'comment':
break;
case 'include': {
const includePath = utils.trimEnd(args.join(' '), ';');
output += utils.trim(await processFile(includePath));
output += "\n";
break;
}
case 'buildvars':
if(typeof options.buildVars === 'object') {
const bvTarget = options.buildVarsTarget || 'window';
const bvProps = [];
for(const bvName in options.buildVars)
bvProps.push(`${bvName}: { value: ${JSON.stringify(options.buildVars[bvName])}, writable: false }`);
if(Object.keys(bvProps).length > 0)
output += `Object.defineProperties(${bvTarget}, { ${bvProps.join(', ')} });\n`;
}
break;
default:
output += line;
output += "\n";
break;
}
} else {
output += line;
output += "\n";
}
}
return output;
};
return await processFile(entryPoint);
};
exports.housekeep = function(assetsPath) {
const files = fs.readdirSync(assetsPath).map(fileName => {
const stats = fs.statSync(path.join(assetsPath, fileName));
return {
name: fileName,
lastMod: stats.mtimeMs,
};
}).sort((a, b) => b.lastMod - a.lastMod).map(info => info.name);
const regex = /^(.+)[\-\.]([a-f0-9]+)\.(.+)$/i;
const counts = {};
for(const fileName of files) {
const match = fileName.match(regex);
if(match) {
const name = match[1] + '-' + match[3];
counts[name] = (counts[name] || 0) + 1;
if(counts[name] > 5)
fs.unlinkSync(path.join(assetsPath, fileName));
}
}
};

195
scripts/eepromv1.js/main.js Normal file
View file

@ -0,0 +1,195 @@
window.EEPROM = (() => {
const errGeneric = 'generic';
const errInvalid = 'invalid';
const errAuth = 'auth';
const errAccess = 'access';
const errDMCA = 'dmca';
const errGone = 'gone';
const errSize = 'size';
const errServer = 'server';
const createClient = function(srcId, endPoint, authorization) {
if(typeof srcId !== 'number')
srcId = parseInt(srcId);
if(typeof endPoint !== 'string')
throw 'endPoint must be a string';
if(typeof authorization !== 'string' && typeof authorization !== 'function')
throw 'authorization must be a string or a function returning a string';
const createUpload = file => {
const uploadTask = { onComplete: undefined, onFailure: undefined, onProgress: undefined };
let userAborted = false;
const xhr = new XMLHttpRequest;
const fd = new FormData;
fd.append('src', srcId);
fd.append('file', file);
const reportUploadProgress = ev => {
if(typeof uploadTask.onProgress === 'function')
uploadTask.onProgress({
loaded: ev.loaded,
total: ev.total,
progress: Math.ceil((ev.loaded / ev.total) * 100),
});
};
xhr.upload.addEventListener('loadstart', reportUploadProgress);
xhr.upload.addEventListener('progress', reportUploadProgress);
xhr.upload.addEventListener('load', reportUploadProgress);
xhr.addEventListener('readystatechange', () => {
if(xhr.readyState !== 4)
return;
if(xhr.status !== 201) {
const failureResponse = {
userAborted: userAborted,
error: errGeneric,
};
switch(xhr.status) {
case 400:
case 405:
failureResponse.error = errInvalid;
break;
case 401:
failureResponse.error = errAuth;
break;
case 403:
failureResponse.error = errAccess;
break;
case 404:
case 410:
failureResponse.error = errGone;
break;
case 451:
failureResponse.error = errDMCA;
break;
case 413:
failureResponse.error = errSize;
failureResponse.maxSize = parseInt(xhr.getResponseHeader('X-EEPROM-Max-Size'));
break;
case 500:
case 503:
failureResponse.error = errServer;
break;
}
if(typeof uploadTask.onFailure === 'function')
uploadTask.onFailure(failureResponse);
return;
}
if(typeof uploadTask.onComplete === 'function') {
const fileInfo = JSON.parse(xhr.responseText);
if(typeof fileInfo !== 'object') {
if(typeof uploadTask.onFailure === 'function')
uploadTask.onFailure({
userAborted: userAborted,
error: errServer,
});
return;
}
fileInfo.isImage = () => typeof fileInfo.type === 'string' && fileInfo.type.indexOf('image/') === 0;
fileInfo.isVideo = () => typeof fileInfo.type === 'string' && fileInfo.type.indexOf('video/') === 0;
fileInfo.isAudio = () => typeof fileInfo.type === 'string' && (fileInfo.type === 'application/x-font-gdos' || fileInfo.type.indexOf('audio/') === 0);
fileInfo.isMedia = () => typeof fileInfo.type === 'string' && (fileInfo.isImage() || fileInfo.isAudio() || fileInfo.isVideo());
uploadTask.onComplete(fileInfo);
}
});
uploadTask.abort = () => {
userAborted = true;
xhr.abort();
};
uploadTask.start = () => {
xhr.open('POST', endPoint);
const authIsFunc = typeof authorization === 'function';
if(authIsFunc || typeof authorization === 'string')
xhr.setRequestHeader('Authorization', authIsFunc ? authorization() : authorization);
else
xhr.withCredentials = true;
xhr.send(fd);
};
return uploadTask;
};
const deleteUpload = fileInfo => {
const deleteTask = { onSuccess: undefined, onFailure: undefined };
const xhr = new XMLHttpRequest;
xhr.addEventListener('readystatechange', () => {
if(xhr.readyState !== 4)
return;
if(xhr.status !== 204) {
let errorCode = errGeneric;
switch(xhr.status) {
case 401:
errorCode = errAuth;
break;
case 403:
errorCode = errAccess;
break;
case 404:
case 410:
errorCode = errGone;
break;
case 500:
case 503:
errorCode = errServer;
break;
}
if(typeof deleteTask.onFailure === 'function')
deleteTask.onFailure(errorCode);
return;
}
if(typeof deleteTask.onSuccess === 'function')
deleteTask.onSuccess();
});
deleteTask.start = () => {
xhr.open('DELETE', fileInfo.urlf);
const authIsFunc = typeof authorization === 'function';
if(authIsFunc || typeof authorization === 'string')
xhr.setRequestHeader('Authorization', authIsFunc ? authorization() : authorization);
else
xhr.withCredentials = true;
xhr.send();
};
return deleteTask;
};
return {
createUpload: createUpload,
deleteUpload: deleteUpload,
};
};
Object.defineProperties(createClient, {
ERR_GENERIC: { value: errGeneric },
ERR_INVALID: { value: errInvalid },
ERR_AUTH: { value: errAuth },
ERR_ACCESS: { value: errAccess },
ERR_DMCA: { value: errDMCA },
ERR_GONE: { value: errGone },
ERR_SERVER: { value: errServer },
ERR_SIZE: { value: errSize },
});
return createClient;
})();

View file

@ -0,0 +1,45 @@
const EEPFMT = (() => {
const symbols = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q'];
const format = (bytes, binary) => {
if(bytes === 0)
return 'Zero Bytes';
const negative = bytes < 0;
const power = binary ? 1024 : 1000;
const exp = Math.floor(Math.log(bytes) / Math.log(power));
bytes = Math.abs(bytes);
const number = bytes / Math.pow(power, exp);
const symbol = symbols[exp];
let string = '';
if(negative)
string += '-';
const fractionDigits = bytes < power ? 0 : (number < 10 ? 2 : 1);
string += number.toLocaleString(undefined, {
maximumFractionDigits: fractionDigits,
minimumFractionDigits: fractionDigits,
});
string += ` ${symbol}`;
if(symbol === '') {
string += 'Byte';
if(number > 1)
string += 's';
} else {
if(binary)
string += 'i';
string += 'B';
}
return string;
};
return {
format: format,
};
})();

View file

@ -0,0 +1,213 @@
#include bytefmt.js
#include xhr.js
const EEPROM = function(appId, endPoint, auth) {
if(typeof appId !== 'string')
appId = (appId || '').toString();
if(typeof endPoint !== 'string')
throw 'endPoint must be a string';
const applyAuth = options => {
if(typeof auth === 'function')
options.headers.Authorization = auth();
else if(typeof auth === 'string')
options.headers.Authorization = auth;
else
options.authed = true;
};
const createUpload = fileInput => {
if(!(fileInput instanceof File))
throw 'fileInput must be an instance of window.File';
let userAborted = false;
let abortHandler, progressHandler;
const reportProgress = ev => {
if(progressHandler !== undefined)
progressHandler({
loaded: ev.loaded,
total: ev.total,
progress: ev.total <= 0 ? 0 : ev.loaded / ev.total,
});
};
const uploadAbortedError = () => {
return {
error: 'eeprom:aborted_error',
aborted: userAborted,
toString: () => 'File upload was aborted manually.',
};
};
const uploadGenericError = () => {
return {
error: 'eeprom:generic_error',
aborted: userAborted,
toString: () => 'File upload failed for unknown reasons.',
};
};
return {
abort: () => {
userAborted = true;
if(abortHandler !== undefined)
abortHandler();
},
onProgress: handler => {
if(typeof handler !== 'function')
throw 'handler must be a function';
progressHandler = handler;
},
start: async () => {
if(userAborted)
throw uploadAbortedError();
const options = {
type: 'json',
headers: {},
upload: reportProgress,
abort: handler => abortHandler = handler,
};
applyAuth(options);
const formData = new FormData;
formData.append('src', appId);
formData.append('file', fileInput);
try {
const result = await EEPXHR.post(`${endPoint}/uploads`, options, formData);
if(result.status !== 201) {
if(result.status === 400)
throw {
error: 'eeprom:request_error',
aborted: userAborted,
toString: () => 'There was an error with the upload request.',
};
if(result.status === 401)
throw {
error: 'eeprom:auth_error',
aborted: userAborted,
toString: () => 'Could not authenticate upload request. If this persists, try refreshing the page.',
};
if(result.status === 403)
throw {
error: 'eeprom:access_error',
aborted: userAborted,
toString: () => 'You are not allowed to upload files.',
};
if(result.status === 404)
throw {
error: 'eeprom:app_error',
aborted: userAborted,
toString: () => 'EEPROM app is not configured properly.',
};
if(result.status === 413) {
const maxSize = parseInt(xhr.headers().get('x-eeprom-max-size'));
const maxSizeFormatted = EEPFMT.format(maxSize);
throw {
error: 'eeprom:size_error',
maxSize: maxSize,
maxSizeFormatted: maxSizeFormatted,
aborted: userAborted,
toString: () => maxSize < 1 ? 'Uploaded file was too large.' : `Uploads may not be larger than ${maxSizeFormatted}.`,
};
}
if(result.status === 451)
throw {
error: 'eeprom:dmca_error',
aborted: userAborted,
toString: () => 'This file is blocked from being uploaded, possibly for copyright reasons.',
};
if(result.status === 500)
throw {
error: 'eeprom:server_error',
aborted: userAborted,
toString: () => 'An error occurred within the EEPROM server. If this persists, report it to a developer.',
};
if(result.status === 503)
throw {
error: 'eeprom:maintenance_error',
aborted: userAborted,
toString: () => 'EEPROM server is temporarily unavailable for maintenance.',
};
throw uploadGenericError();
}
const fileInfo = result.body();
if(typeof fileInfo.type === 'string') {
fileInfo.isImage = () => fileInfo.type.indexOf('image/') === 0;
fileInfo.isVideo = () => fileInfo.type.indexOf('video/') === 0;
fileInfo.isAudio = () => fileInfo.type === 'application/x-font-gdos' || fileInfo.type.indexOf('audio/') === 0;
} else
fileInfo.isImage = fileInfo.isVideo = fileInfo.isAudio = () => false;
fileInfo.isMedia = () => fileInfo.isImage() || fileInfo.isAudio() || fileInfo.isVideo();
return Object.freeze(fileInfo);
} catch(ex) {
if(!ex.abort) {
console.error(ex);
throw uploadGenericError();
}
throw uploadAbortedError();
}
},
};
};
const deleteUpload = async fileInfo => {
if(typeof fileInfo !== 'object')
throw 'fileInfo must be an object';
if(typeof fileInfo.urlf !== 'string')
throw 'fileInfo.urlf must be a string';
const options = { headers: {} };
applyAuth(options);
const result = await EEPXHR.delete(fileInfo.urlf, options);
if(result.status !== 204) {
if(result.status === 401)
throw {
error: 'eeprom:auth_error',
toString: () => 'Could not authenticate delete request. If this persists, try refreshing the page.',
};
if(result.status === 403)
throw {
error: 'eeprom:access_error',
toString: () => 'You are not allowed to delete this file.',
};
if(result.status === 404)
throw {
error: 'eeprom:file_error',
toString: () => 'File not found.',
};
if(result.status === 500)
throw {
error: 'eeprom:server_error',
toString: () => 'An error occurred within the EEPROM server. If this persists, report it to a developer.',
};
if(result.status === 503)
throw {
error: 'eeprom:maintenance_error',
toString: () => 'EEPROM server is temporarily unavailable for maintenance.',
};
throw {
error: 'eeprom:generic_error',
toString: () => 'File delete failed for unknown reasons.',
};
}
};
return {
create: createUpload,
delete: deleteUpload,
};
};

View file

@ -0,0 +1,75 @@
const EEPXHR = (() => {
const send = function(method, url, options, body) {
const xhr = new XMLHttpRequest;
const requestHeaders = new Map;
if('headers' in options && typeof options.headers === 'object')
for(const name in options.headers)
if(options.headers.hasOwnProperty(name))
requestHeaders.set(name.toLowerCase(), options.headers[name]);
if(typeof options.upload === 'function') {
xhr.upload.onloadstart = ev => options.upload(ev);
xhr.upload.onprogress = ev => options.upload(ev);
xhr.upload.onloadend = ev => options.upload(ev);
}
if(options.authed)
xhr.withCredentials = true;
if(typeof options.timeout === 'number')
xhr.timeout = options.timeout;
if(typeof options.type === 'string')
xhr.responseType = options.type;
if(typeof options.abort === 'function')
options.abort(() => xhr.abort());
return new Promise((resolve, reject) => {
let responseHeaders = undefined;
xhr.onload = ev => resolve({
status: xhr.status,
body: () => xhr.response,
headers: () => {
if(responseHeaders !== undefined)
return responseHeaders;
responseHeaders = new Map;
const raw = xhr.getAllResponseHeaders().trim().split(/[\r\n]+/);
for(const name in raw)
if(raw.hasOwnProperty(name)) {
const parts = raw[name].split(': ');
responseHeaders.set(parts.shift(), parts.join(': '));
}
return responseHeaders;
},
});
xhr.onabort = ev => reject({
abort: true,
xhr: xhr,
ev: ev,
});
xhr.onerror = ev => reject({
abort: false,
xhr: xhr,
ev: ev,
});
xhr.open(method, url);
for(const [name, value] of requestHeaders)
xhr.setRequestHeader(name, value);
xhr.send(body);
});
};
return {
post: (url, options, body) => send('POST', url, options, body),
delete: (url, options, body) => send('DELETE', url, options, body),
};
})();

35
scripts/utils.js Normal file
View file

@ -0,0 +1,35 @@
const crypto = require('crypto');
exports.strtr = (str, replacements) => str.toString().replace(
/{([^}]+)}/g, (match, key) => replacements[key] || match
);
const trim = function(str, chars, flags) {
if(chars === undefined)
chars = " \n\r\t\v\0";
let start = 0,
end = str.length;
if(flags & 0x01)
while(start < end && chars.indexOf(str[start]) >= 0)
++start;
if(flags & 0x02)
while(end > start && chars.indexOf(str[end - 1]) >= 0)
--end;
return (start > 0 || end < str.length)
? str.substring(start, end)
: str;
};
exports.trimStart = (str, chars) => trim(str, chars, 0x01);
exports.trimEnd = (str, chars) => trim(str, chars, 0x02);
exports.trim = (str, chars) => trim(str, chars, 0x03);
exports.shortHash = function(text) {
const hash = crypto.createHash('sha256');
hash.update(text);
return hash.digest('hex').substring(0, 8);
};

View file

@ -42,7 +42,7 @@ class LandingRoutes extends RouteHandler {
#[Route('GET', '/eeprom.js')]
public function getEepromJs($response) {
$response->accelRedirect('/js/eeprom-v1.0.js');
$response->accelRedirect('/scripts/eepromv1.js');
$response->setContentType('application/javascript; charset=utf-8');
}
}