fix: eslint autofixable errors (#2002)

This commit is contained in:
Daniel Salazar
2025-11-21 13:22:19 -08:00
committed by GitHub
parent f1773974c0
commit 11e057557d
929 changed files with 58669 additions and 53640 deletions

View File

@@ -4,23 +4,23 @@ import events from './events.json.js';
const mdlib = {};
mdlib.h = (out, n, str) => {
out(`${'#'.repeat(n)} ${str}\n\n`);
}
};
const N_START = 3;
const out = str => process.stdout.write(str);
for ( const event of events ) {
mdlib.h(out, N_START, `\`${event.id}\``);
out(dedent(event.description) + '\n\n');
out(`${dedent(event.description) }\n\n`);
for ( const k in event.properties ) {
const prop = event.properties[k];
mdlib.h(out, N_START + 1, `Property \`${k}\``);
out(prop.summary + '\n');
out(`${prop.summary }\n`);
out(`- **Type**: ${prop.type}\n`);
out(`- **Mutability**: ${prop.mutability}\n`);
if ( prop.notes ) {
out(`- **Notes**:\n`);
out('- **Notes**:\n');
for ( const note of prop.notes ) {
out(` - ${note}\n`);
}
@@ -29,7 +29,7 @@ for ( const event of events ) {
}
if ( event.example ) {
mdlib.h(out, N_START + 1, `Example`);
mdlib.h(out, N_START + 1, 'Example');
out(`\`\`\`${event.example.language}\n${dedent(event.example.code)}\n\`\`\`\n`);
}

View File

@@ -12,7 +12,7 @@ export default [
summary: 'the email being validated',
notes: [
'The email may have already been cleaned.',
]
],
},
allow: {
type: 'boolean',
@@ -20,7 +20,7 @@ export default [
summary: 'whether the email is allowed',
notes: [
'If set to false, the email will be considered invalid.',
]
],
},
},
},
@@ -44,8 +44,8 @@ export default [
measurements: data.measurements
});
});
`
}
`,
},
},
{
id: 'core.fs.create.directory',
@@ -61,8 +61,8 @@ export default [
context: {
type: 'Context',
mutability: 'no-effect',
summary: 'current context'
summary: 'current context',
},
}
},
},
];

View File

@@ -8,8 +8,8 @@ export default [
`,
example_values: [
'example.com',
'subdomain.example.com'
]
'subdomain.example.com',
],
},
{
key: 'protocol',
@@ -18,8 +18,8 @@ export default [
`,
example_values: [
'http',
'https'
]
'https',
],
},
{
key: 'static_hosting_domain',
@@ -30,7 +30,7 @@ export default [
you could set this to something like
\`site.192.168.555.12.nip.io\`, replacing
\`192.168.555.12\` with a valid IP address belonging to the server.
`
`,
},
{
key: 'allow_all_host_values',
@@ -44,7 +44,7 @@ export default [
description: `
If true, Puter will allow requests with host headers that end in nip.io.
This is useful for development, LAN, and VPN configurations.
`
`,
},
{
key: 'http_port',
@@ -57,21 +57,21 @@ export default [
description: `
If true, any /username/Public directory will be available to all
users, including anonymous users.
`
`,
},
{
key: 'disable_temp_users',
description: `
If true, new users will see the login/signup page instead of being
automatically logged in as a temporary user.
`
`,
},
{
key: 'disable_user_signup',
description: `
If true, the signup page will be disabled and the backend will not
accept new user registrations.
`
`,
},
{
key: 'disable_fallback_mechanisms',
@@ -79,6 +79,6 @@ export default [
A general setting to prevent any fallback behavior that might
"hide" errors. It is recommended to set this to true when
debugging, testing, or developing new features.
`
}
]
`,
},
];

View File

@@ -4,17 +4,17 @@ import configVals from './config-vals.json.js';
const mdlib = {};
mdlib.h = (out, n, str) => {
out(`${'#'.repeat(n)} ${str}\n\n`);
}
};
const N_START = 3;
const out = str => process.stdout.write(str);
for ( const configVal of configVals ) {
mdlib.h(out, N_START, `\`${configVal.key}\``);
out(dedent(configVal.description) + '\n\n');
out(`${dedent(configVal.description) }\n\n`);
if ( configVal.example_values ) {
mdlib.h(out, N_START + 1, `Examples`);
mdlib.h(out, N_START + 1, 'Examples');
for ( const example of configVal.example_values ) {
out(`- \`"${configVal.key}": ${JSON.stringify(example)}\`\n`);
}

View File

@@ -10,15 +10,14 @@ import spaceUnaryOpsWithException from './eslint/space-unary-ops-with-exception.
const rules = {
'no-unused-vars': ['error', {
'vars': 'all',
'args': 'after-used',
'caughtErrors': 'all',
'ignoreRestSiblings': false,
'ignoreUsingDeclarations': false,
'reportUsedIgnorePattern': false,
'argsIgnorePattern': '^_',
'caughtErrorsIgnorePattern': '^_',
'destructuredArrayIgnorePattern': '^_',
vars: 'all',
args: 'after-used',
caughtErrors: 'none',
ignoreRestSiblings: false,
ignoreUsingDeclarations: false,
reportUsedIgnorePattern: false,
argsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
}],
curly: ['error', 'multi-line'],
@@ -60,7 +59,7 @@ export default defineConfig([
// TypeScript support block
{
files: ['**/*.ts'],
ignores: ['tests/**/*.ts', 'extensions'],
ignores: ['tests/**/*.ts', 'extensions/**/*.ts'],
languageOptions: {
parser: tseslintParser,
parserOptions: {
@@ -75,7 +74,7 @@ export default defineConfig([
rules: {
// Recommended rules for TypeScript
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrors: 'none' }],
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
},
@@ -88,7 +87,7 @@ export default defineConfig([
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: 'extensions/tsconfig.json',
project: './extensions/tsconfig.json',
},
},
plugins: {
@@ -97,16 +96,18 @@ export default defineConfig([
rules: {
// Recommended rules for TypeScript
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrors: 'none' }],
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
},
},
} },
// TypeScript support for tests
{
files: ['tests/**/*.ts'],
ignores: ['tests/playwright/tests/**/*.ts'],
languageOptions: {
parser: tseslintParser,
globals: { ...globals.jest, ...globals.node },
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
@@ -119,11 +120,10 @@ export default defineConfig([
rules: {
// Recommended rules for TypeScript
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrors: 'none' }],
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
},
},
} },
{
plugins: {
js,
@@ -136,7 +136,14 @@ export default defineConfig([
},
},
{
files: ['src/backend/**/*.{js,mjs,cjs,ts}'],
files: [
'src/backend/**/*.{js,mjs,cjs,ts}',
'src/backend-core-0/**/*.{js,mjs,cjs,ts}',
'src/putility/**/*.{js,mjs,cjs,ts}',
],
ignores: [
'**/*.test.js',
],
languageOptions: { globals: globals.node },
rules,
extends: ['js/recommended'],
@@ -145,6 +152,17 @@ export default defineConfig([
'@stylistic': stylistic,
},
},
{
files: [
'**/*.test.js',
],
languageOptions: { globals: { ...globals.jest, ...globals.node } },
rules,
plugins: {
js,
'@stylistic': stylistic,
},
},
{
files: ['extensions/**/*.{js,mjs,cjs,ts}'],
languageOptions: {
@@ -163,28 +181,29 @@ export default defineConfig([
},
},
{
files: ['**/*.{js,mjs,cjs,ts}'],
files: ['**/*.{js,mjs,cjs,ts}', 'src/gui/src/**/*.js'],
ignores: [
'src/backend/**/*.{js,mjs,cjs,ts}',
'extensions/**/*.{js,mjs,cjs,ts}',
'src/backend-core-0/**/*.{js,mjs,cjs,ts}',
'submodules/**',
'tests/**',
'tools/**',
'**/*.min.js',
'**/*.min.cjs',
'**/*.min.mjs',
'**/socket.io.js',
'**/dist/*.js',
'src/phoenix/test/**',
'src/gui/src/lib/**',
'src/gui/dist/**',
],
languageOptions: {
globals: {
...globals.browser,
...globals.jquery,
i18n: 'readonly',
},
},
rules,
},
{
files: ['**/*.{js,mjs,cjs,ts}'],
ignores: ['src/backend/**/*.{js,mjs,cjs,ts}'],
languageOptions: {
globals: {
...globals.browser,
...globals.jquery,
i18n: 'readonly',
puter: 'readonly',
},
},
rules,
@@ -192,7 +211,6 @@ export default defineConfig([
plugins: {
js,
'@stylistic': stylistic,
},
},
]);

View File

@@ -29,7 +29,7 @@ export default {
return {
IfStatement (ifNode) {
const testRaw = ifNode.test;
if ( !testRaw ) return;
if ( ! testRaw ) return;
const test = unwrapParens(testRaw);
if ( !test || test.type !== 'UnaryExpression' || test.operator !== '!' ) {

View File

@@ -15,10 +15,10 @@ export default {
},
},
create(context) {
create (context) {
const sourceCode = context.getSourceCode();
function checkControlStructureSpacing(node) {
function checkControlStructureSpacing (node) {
// For control structures, we need to find the parentheses around the condition/test
let conditionNode;
@@ -33,7 +33,7 @@ export default {
conditionNode = node.param;
}
if ( !conditionNode ) return;
if ( ! conditionNode ) return;
// Find the opening paren - it should be right before the condition starts
const openParen = sourceCode.getTokenBefore(conditionNode, token => token.value === '(');
@@ -62,7 +62,7 @@ export default {
node,
loc: openParen.loc,
messageId: 'missingSpaceAfterOpen',
fix(fixer) {
fix (fixer) {
return fixer.insertTextAfter(openParen, ' ');
},
});
@@ -73,25 +73,25 @@ export default {
node,
loc: closeParen.loc,
messageId: 'missingSpaceBeforeClose',
fix(fixer) {
fix (fixer) {
return fixer.insertTextBefore(closeParen, ' ');
},
});
}
}
function checkForLoopSpacing(node) {
function checkForLoopSpacing (node) {
// For loops are special - we need to find the opening paren after the 'for' keyword
// and the closing paren before the body
const forKeyword = sourceCode.getFirstToken(node);
if ( !forKeyword || forKeyword.value !== 'for' ) return;
const openParen = sourceCode.getTokenAfter(forKeyword, token => token.value === '(');
if ( !openParen ) return;
if ( ! openParen ) return;
// The closing paren should be right before the body
const closeParen = sourceCode.getTokenBefore(node.body, token => token.value === ')');
if ( !closeParen ) return;
if ( ! closeParen ) return;
const afterOpen = sourceCode.getTokenAfter(openParen);
const beforeClose = sourceCode.getTokenBefore(closeParen);
@@ -101,7 +101,7 @@ export default {
node,
loc: openParen.loc,
messageId: 'missingSpaceAfterOpen',
fix(fixer) {
fix (fixer) {
return fixer.insertTextAfter(openParen, ' ');
},
});
@@ -112,14 +112,14 @@ export default {
node,
loc: closeParen.loc,
messageId: 'missingSpaceBeforeClose',
fix(fixer) {
fix (fixer) {
return fixer.insertTextBefore(closeParen, ' ');
},
});
}
}
function checkFunctionCallSpacing(node) {
function checkFunctionCallSpacing (node) {
// Find the opening parenthesis for this function call
const openParen = sourceCode.getFirstToken(node, token => token.value === '(');
const closeParen = sourceCode.getLastToken(node, token => token.value === ')');
@@ -137,7 +137,7 @@ export default {
node,
loc: openParen.loc,
messageId: 'unexpectedSpaceAfterOpen',
fix(fixer) {
fix (fixer) {
return fixer.removeRange([openParen.range[1], afterOpen.range[0]]);
},
});
@@ -151,7 +151,7 @@ export default {
node,
loc: closeParen.loc,
messageId: 'unexpectedSpaceBeforeClose',
fix(fixer) {
fix (fixer) {
return fixer.removeRange([beforeClose.range[1], closeParen.range[0]]);
},
});
@@ -161,40 +161,40 @@ export default {
return {
// Control structures that should have spacing
IfStatement(node) {
IfStatement (node) {
checkControlStructureSpacing(node);
},
WhileStatement(node) {
WhileStatement (node) {
checkControlStructureSpacing(node);
},
DoWhileStatement(node) {
DoWhileStatement (node) {
checkControlStructureSpacing(node);
},
SwitchStatement(node) {
SwitchStatement (node) {
checkControlStructureSpacing(node);
},
CatchClause(node) {
CatchClause (node) {
if ( node.param ) {
checkControlStructureSpacing(node);
}
},
// For loops need special handling
ForStatement(node) {
ForStatement (node) {
checkForLoopSpacing(node);
},
ForInStatement(node) {
ForInStatement (node) {
checkForLoopSpacing(node);
},
ForOfStatement(node) {
ForOfStatement (node) {
checkForLoopSpacing(node);
},
// Function calls that should NOT have spacing
CallExpression(node) {
CallExpression (node) {
checkFunctionCallSpacing(node);
},
NewExpression(node) {
NewExpression (node) {
if ( node.arguments.length > 0 || sourceCode.getLastToken(node).value === ')' ) {
checkFunctionCallSpacing(node);
}

View File

@@ -1,7 +1,7 @@
//@puter priority -1
console.log('exporting something...');
extension.exports = {
testval: 5
testval: 5,
};
extension.on('init', () => {

View File

@@ -5,7 +5,7 @@ extension.get('/metering/usage', { subdomain: 'api' }, async (req, res) => {
const meteringService = meteringServiceWrapper.meteringService;
const actor = req.actor;
if ( !actor ) {
if ( ! actor ) {
throw Error('actor not found in context');
}
const actorUsagePromise = meteringService.getActorCurrentMonthUsageDetails(actor);
@@ -20,11 +20,11 @@ extension.get('/metering/usage/:appId', { subdomain: 'api' }, async (req, res) =
const meteringService = meteringServiceWrapper.meteringService;
const actor = req.actor;
if ( !actor ) {
if ( ! actor ) {
throw Error('actor not found in context');
}
const appId = req.params.appId;
if ( !appId ) {
if ( ! appId ) {
res.status(400).json({ error: 'appId parameter is required' });
return;
}
@@ -37,13 +37,13 @@ extension.get('/metering/usage/:appId', { subdomain: 'api' }, async (req, res) =
extension.get('/metering/globalUsage', { subdomain: 'api' }, async (req, res) => {
const meteringService = meteringServiceWrapper.meteringService;
const actor = req.actor;
if ( !actor ) {
if ( ! actor ) {
throw Error('actor not found in context');
}
// check if actor is allowed to view global usage
const allowedUsers = extension.config.allowedGlobalUsageUsers || [];
if ( !allowedUsers.includes(actor.type?.user.uuid) ) {
if ( ! allowedUsers.includes(actor.type?.user.uuid) ) {
res.status(403).json({ error: 'You are not authorized to view global usage' });
return;
}

View File

@@ -1,18 +1,18 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
*
* This file is part of Puter.
*
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -23,4 +23,4 @@ extension.get('/example-onefile-get', (req, res) => {
extension.on('install', ({ services }) => {
// console.log('install was called');
})
});

View File

@@ -1,18 +1,18 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
*
* This file is part of Puter.
*
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -23,4 +23,4 @@ extension.get('/example-mod-get', (req, res) => {
extension.on('install', ({ services }) => {
// console.log('install was called');
})
});

View File

@@ -22,7 +22,7 @@ class CustomPuterService extends use.Service {
async _init () {
const svc_commands = this.services.get('commands');
this._register_commands(svc_commands);
const svc_puterHomepage = this.services.get('puter-homepage');
svc_puterHomepage.register_script('/custom-gui/main.js');
}
@@ -32,29 +32,28 @@ class CustomPuterService extends use.Service {
const path_ = require('path');
app.use('/custom-gui',
express.static(path.join(__dirname, 'gui')));
express.static(path.join(__dirname, 'gui')));
}
async ['__on_boot.consolidation'] () {
const then = Date.now();
this.tod_widget = () => {
const s = 5 - Math.floor(
(Date.now() - then) / 1000);
const s = 5 - Math.floor((Date.now() - then) / 1000);
const lines = [
"\x1B[36;1mKDMOD ENABLED\x1B[0m" +
` (👁️ ${s}s)`
'\x1B[36;1mKDMOD ENABLED\x1B[0m' +
` (👁️ ${s}s)`,
];
// It would be super cool to be able to use this here
// surrounding_box('33;1', lines);
return lines;
}
};
const svc_devConsole = this.services.get('dev-console', { optional: true });
if ( ! svc_devConsole ) return;
svc_devConsole.add_widget(this.tod_widget);
setTimeout(() => {
svc_devConsole.remove_widget(this.tod_widget);
}, 5000)
}, 5000);
}
_register_commands (commands) {
@@ -69,8 +68,8 @@ class CustomPuterService extends use.Service {
const lines = this.tod_widget();
for ( const line of lines ) log.log(line);
this.tod_widget = null;
}
}
},
},
]);
}
}

View File

@@ -41,7 +41,7 @@ class ShareTestService extends use.Service {
uuidv4: require('uuid').v4,
};
async _init() {
async _init () {
const svc_commands = this.services.get('commands');
this._register_commands(svc_commands);
@@ -51,7 +51,7 @@ class ShareTestService extends use.Service {
this.db = svc_db.get(svc_db.DB_WRITE, 'share-test');
}
_register_commands(commands) {
_register_commands (commands) {
commands.registerCommands('share-test', [
{
id: 'start',
@@ -74,7 +74,7 @@ class ShareTestService extends use.Service {
]);
}
async runit() {
async runit () {
await this.teardown_();
await this.setup_();
@@ -94,13 +94,13 @@ class ShareTestService extends use.Service {
return results;
}
async setup_() {
async setup_ () {
await this.create_test_user_('testuser_eric');
await this.create_test_user_('testuser_stan');
await this.create_test_user_('testuser_kyle');
await this.create_test_user_('testuser_kenny');
}
async run_scenario_(scenario) {
async run_scenario_ (scenario) {
let error;
// Run sequence
for ( const step of scenario.sequence ) {
@@ -119,14 +119,14 @@ class ShareTestService extends use.Service {
}
return error;
}
async teardown_() {
async teardown_ () {
await this.delete_test_user_('testuser_eric');
await this.delete_test_user_('testuser_stan');
await this.delete_test_user_('testuser_kyle');
await this.delete_test_user_('testuser_kenny');
}
async create_test_user_(username) {
async create_test_user_ (username) {
await this.db.write(`
INSERT INTO user (uuid, username, email, free_storage, password)
VALUES (?, ?, ?, ?, ?)
@@ -145,14 +145,14 @@ class ShareTestService extends use.Service {
return user;
}
async delete_test_user_(username) {
async delete_test_user_ (username) {
const user = await get_user({ username });
if ( ! user ) return;
await deleteUser(user.id);
}
// API for scenarios
async ['__scenario:create-example-file'](
async ['__scenario:create-example-file'] (
{ actor, user },
{ name, contents },
) {
@@ -178,7 +178,7 @@ class ShareTestService extends use.Service {
file,
});
}
async ['__scenario:assert-no-access'](
async ['__scenario:assert-no-access'] (
{ actor, user },
{ path },
) {
@@ -190,21 +190,21 @@ class ShareTestService extends use.Service {
fsNode: node,
actor,
});
} catch(e) {
} catch (e) {
expected_e = e;
}
if ( ! expected_e ) {
return { message: 'expected error, got none' };
}
}
async ['__scenario:grant'](
async ['__scenario:grant'] (
{ actor, user },
{ to, permission },
) {
const svc_permission = this.services.get('permission');
await svc_permission.grant_user_user_permission(actor, to, permission, {}, {});
}
async ['__scenario:assert-access'](
async ['__scenario:assert-access'] (
{ actor, user },
{ path, level },
) {

View File

@@ -26,17 +26,17 @@ module.exports = [
with: {
name: 'example.txt',
contents: 'secret file',
}
},
},
{
title: 'Eric tries to access it',
call: 'assert-no-access',
as: 'testuser_eric',
with: {
path: '/testuser_kyle/Desktop/example.txt'
}
path: '/testuser_kyle/Desktop/example.txt',
},
},
]
],
},
{
sequence: [
@@ -47,7 +47,7 @@ module.exports = [
with: {
name: 'example.txt',
contents: 'secret file',
}
},
},
{
title: 'Stan grants permission to Eric',
@@ -55,8 +55,8 @@ module.exports = [
as: 'testuser_stan',
with: {
to: 'testuser_eric',
permission: 'fs:/testuser_stan/Desktop/example.txt:read'
}
permission: 'fs:/testuser_stan/Desktop/example.txt:read',
},
},
{
title: 'Eric tries to access it',
@@ -64,10 +64,10 @@ module.exports = [
as: 'testuser_eric',
with: {
path: '/testuser_stan/Desktop/example.txt',
level: 'read'
}
level: 'read',
},
},
]
],
},
{
sequence: [
@@ -77,8 +77,8 @@ module.exports = [
as: 'testuser_stan',
with: {
to: 'testuser_eric',
permission: 'fs:/testuser_kyle/Desktop/example.txt:read'
}
permission: 'fs:/testuser_kyle/Desktop/example.txt:read',
},
},
{
title: 'Eric tries to access it',
@@ -86,8 +86,8 @@ module.exports = [
as: 'testuser_eric',
with: {
path: '/testuser_kyle/Desktop/example.txt',
}
},
},
]
],
},
];

View File

@@ -22,15 +22,15 @@ const request_examples = [
fetch: async (args) => {
return await fetch(`${window.api_origin}/drivers/call`, {
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${puter.authToken}`,
'Content-Type': 'application/json',
'Authorization': `Bearer ${puter.authToken}`,
},
body: JSON.stringify({
interface: 'puter-apps',
method: 'read',
args,
}),
method: "POST",
method: 'POST',
});
},
out: async (resp) => {
@@ -48,15 +48,15 @@ const request_examples = [
fetch: async () => {
return await fetch(`${window.api_origin}/drivers/call`, {
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${puter.authToken}`,
'Content-Type': 'application/json',
'Authorization': `Bearer ${puter.authToken}`,
},
body: JSON.stringify({
interface: 'puter-apps',
method: 'select',
args: { predicate: [] },
}),
method: "POST",
method: 'POST',
});
},
out: async (resp) => {
@@ -73,15 +73,15 @@ const request_examples = [
name: 'grant permission from a user to a user',
fetch: async (user, perm) => {
return await fetch(`${window.api_origin}/auth/grant-user-user`, {
"headers": {
"Content-Type": "application/json",
"Authorization": `Bearer ${puter.authToken}`,
},
"body": JSON.stringify({
target_username: user,
permission: perm,
}),
"method": "POST",
'headers': {
'Content-Type': 'application/json',
'Authorization': `Bearer ${puter.authToken}`,
},
'body': JSON.stringify({
target_username: user,
permission: perm,
}),
'method': 'POST',
});
},
out: async (resp) => {
@@ -98,7 +98,7 @@ const request_examples = [
fetch: async (path, str) => {
const endpoint = `${window.api_origin}/write`;
const token = puter.authToken;
const blob = new Blob([str], { type: 'text/plain' });
const formData = new FormData();
formData.append('create_missing_ancestors', true);
@@ -106,15 +106,15 @@ const request_examples = [
formData.append('size', 8);
formData.append('overwrite', true);
formData.append('file', blob, 'something.txt');
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: formData
body: formData,
});
return await response.json();
},
}
},
];
globalThis.reqex = request_examples;

View File

@@ -19,7 +19,7 @@
extension.on('install', ({ services }) => {
const { CustomPuterService } = require('./CustomPuterService.js');
services.registerService('__custom-puter', CustomPuterService);
const { ShareTestService } = require('./ShareTestService.js');
services.registerService('__share-test', ShareTestService);
});

View File

@@ -12,6 +12,7 @@
},
"devDependencies": {
"@eslint/js": "^9.35.0",
"@playwright/test": "^1.56.1",
"@stylistic/eslint-plugin": "^5.3.1",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.46.1",
@@ -35,7 +36,8 @@
"vite-plugin-static-copy": "^3.1.3",
"vitest": "^3.2.4",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.1"
"webpack-cli": "^5.1.1",
"yaml": "^2.8.1"
},
"scripts": {
"test": "npx mocha src/phoenix/test && npx vitest run src/backend && node src/backend/tools/test",
@@ -89,4 +91,4 @@
"engines": {
"node": ">=20.19.5"
}
}
}

View File

@@ -1,25 +1,25 @@
export const is_valid_uuid = ( uuid ) => {
let s = "" + uuid;
let s = `${ uuid}`;
s = s.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
return !! s;
}
return !!s;
};
export const is_valid_uuid4 = ( uuid ) => {
return is_valid_uuid(uuid);
}
};
export const is_specifically_uuidv4 = ( uuid ) => {
let s = "" + uuid;
let s = `${ uuid}`;
s = s.match(/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i);
if (!s) {
return false;
if ( ! s ) {
return false;
}
return true;
}
};
export const is_valid_url = ( url ) => {
let s = "" + url;
let s = `${ url}`;
try {
new URL(s);
@@ -27,7 +27,7 @@ export const is_valid_url = ( url ) => {
} catch (e) {
return false;
}
}
};
const path_excludes = () => /[\x00-\x1F]/g;
@@ -40,8 +40,8 @@ const safety_excludes = [
/[\u2066-\u2069]/, // RTL and LTR isolate
/[\u2028-\u2029]/, // line and paragraph separator
/[\uFF01-\uFF5E]/, // fullwidth ASCII
/[\u2060]/, // word joiner
/[\uFEFF]/, // zero width no-break space
/[\u2060]/, // word joiner
/[\uFEFF]/, // zero width no-break space
/[\uFFFE-\uFFFF]/, // non-characters
];
@@ -56,8 +56,10 @@ export const is_valid_path = (path, {
if ( exclude.test(path) ) return false;
}
if ( ! allow_path_fragment ) if ( path[0] !== '/' && path[0] !== '.' ) {
return false;
if ( ! allow_path_fragment ) {
if ( path[0] !== '/' && path[0] !== '.' ) {
return false;
}
}
if ( no_relative_components ) {
@@ -70,4 +72,4 @@ export const is_valid_path = (path, {
}
return true;
}
};

View File

@@ -51,4 +51,3 @@ const cjsConfig = {
};
export default [esmConfig, cjsConfig];

View File

@@ -16,33 +16,33 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const CoreModule = require("./src/CoreModule.js");
const { Kernel } = require("./src/Kernel.js");
const DatabaseModule = require("./src/DatabaseModule.js");
const LocalDiskStorageModule = require("./src/LocalDiskStorageModule.js");
const MemoryStorageModule = require("./src/MemoryStorageModule.js");
const SelfHostedModule = require("./src/modules/selfhosted/SelfHostedModule.js");
const { testlaunch } = require("./src/index.js");
const BaseService = require("./src/services/BaseService.js");
const { Context } = require("./src/util/context.js");
const { TestDriversModule } = require("./src/modules/test-drivers/TestDriversModule.js");
const { PuterAIModule } = require("./src/modules/puterai/PuterAIModule.js");
const { BroadcastModule } = require("./src/modules/broadcast/BroadcastModule.js");
const { WebModule } = require("./src/modules/web/WebModule.js");
const { Core2Module } = require("./src/modules/core/Core2Module.js");
const { TemplateModule } = require("./src/modules/template/TemplateModule.js");
const { PuterFSModule } = require("./src/modules/puterfs/PuterFSModule.js");
const { PerfMonModule } = require("./src/modules/perfmon/PerfMonModule.js");
const { AppsModule } = require("./src/modules/apps/AppsModule.js");
const { DevelopmentModule } = require("./src/modules/development/DevelopmentModule.js");
const { HostOSModule } = require("./src/modules/hostos/HostOSModule.js");
const { InternetModule } = require("./src/modules/internet/InternetModule.js");
const { CaptchaModule } = require("./src/modules/captcha/CaptchaModule.js");
const { EntityStoreModule } = require("./src/modules/entitystore/EntityStoreModule.js");
const { KVStoreModule } = require("./src/modules/kvstore/KVStoreModule.js");
const { DomainModule } = require("./src/modules/domain/DomainModule.js");
const { DNSModule } = require("./src/modules/dns/DNSModule.js");
const { TestConfigModule } = require("./src/modules/test-config/TestConfigModule.js");
const CoreModule = require('./src/CoreModule.js');
const { Kernel } = require('./src/Kernel.js');
const DatabaseModule = require('./src/DatabaseModule.js');
const LocalDiskStorageModule = require('./src/LocalDiskStorageModule.js');
const MemoryStorageModule = require('./src/MemoryStorageModule.js');
const SelfHostedModule = require('./src/modules/selfhosted/SelfHostedModule.js');
const { testlaunch } = require('./src/index.js');
const BaseService = require('./src/services/BaseService.js');
const { Context } = require('./src/util/context.js');
const { TestDriversModule } = require('./src/modules/test-drivers/TestDriversModule.js');
const { PuterAIModule } = require('./src/modules/puterai/PuterAIModule.js');
const { BroadcastModule } = require('./src/modules/broadcast/BroadcastModule.js');
const { WebModule } = require('./src/modules/web/WebModule.js');
const { Core2Module } = require('./src/modules/core/Core2Module.js');
const { TemplateModule } = require('./src/modules/template/TemplateModule.js');
const { PuterFSModule } = require('./src/modules/puterfs/PuterFSModule.js');
const { PerfMonModule } = require('./src/modules/perfmon/PerfMonModule.js');
const { AppsModule } = require('./src/modules/apps/AppsModule.js');
const { DevelopmentModule } = require('./src/modules/development/DevelopmentModule.js');
const { HostOSModule } = require('./src/modules/hostos/HostOSModule.js');
const { InternetModule } = require('./src/modules/internet/InternetModule.js');
const { CaptchaModule } = require('./src/modules/captcha/CaptchaModule.js');
const { EntityStoreModule } = require('./src/modules/entitystore/EntityStoreModule.js');
const { KVStoreModule } = require('./src/modules/kvstore/KVStoreModule.js');
const { DomainModule } = require('./src/modules/domain/DomainModule.js');
const { DNSModule } = require('./src/modules/dns/DNSModule.js');
const { TestConfigModule } = require('./src/modules/test-config/TestConfigModule.js');
module.exports = {
helloworld: () => {
@@ -56,7 +56,7 @@ module.exports = {
Context,
Kernel,
EssentialModules: [
Core2Module,
PuterFSModule,
@@ -69,7 +69,7 @@ module.exports = {
EntityStoreModule,
KVStoreModule,
],
// Pre-built modules
CoreModule,
WebModule,
@@ -86,7 +86,7 @@ module.exports = {
KVStoreModule,
DNSModule,
DomainModule,
// Development modules
PerfMonModule,
DevelopmentModule,

View File

@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { AdvancedBase } = require("@heyputer/putility");
const { AdvancedBase } = require('@heyputer/putility');
class DatabaseModule extends AdvancedBase {
async install (context) {
@@ -28,8 +28,8 @@ class DatabaseModule extends AdvancedBase {
strategy_key: 'engine',
strategies: {
sqlite: [SqliteDatabaseAccessService],
}
})
},
});
}
}

View File

@@ -40,7 +40,7 @@ class Extension extends AdvancedBase {
}),
];
randomBrightColor() {
randomBrightColor () {
// Bright colors in ANSI (foreground codes 9097)
const brightColors = [
// 91, // Bright Red
@@ -54,7 +54,7 @@ class Extension extends AdvancedBase {
return brightColors[Math.floor(Math.random() * brightColors.length)];
}
constructor(...a) {
constructor (...a) {
super(...a);
this.service = null;
this.log = null;
@@ -97,18 +97,18 @@ class Extension extends AdvancedBase {
};
}
example() {
example () {
console.log('Example method called by an extension.');
}
// === [START] RuntimeModule aliases ===
set exports(value) {
set exports (value) {
this.runtime.exports = value;
}
get exports() {
get exports () {
return this.runtime.exports;
}
import(name) {
import (name) {
return this.runtime.import(name);
}
// === [END] RuntimeModule aliases ===
@@ -116,7 +116,7 @@ class Extension extends AdvancedBase {
/**
* This will get a database instance from the default service.
*/
get db() {
get db () {
const db = this.service.values.get('db');
if ( ! db ) {
throw new Error('extension tried to access database before it was ' +
@@ -125,7 +125,7 @@ class Extension extends AdvancedBase {
return db;
}
get services() {
get services () {
const services = this.service.values.get('services');
if ( ! services ) {
throw new Error('extension tried to access "services" before it was ' +
@@ -134,7 +134,7 @@ class Extension extends AdvancedBase {
return services;
}
get log_context() {
get log_context () {
const log_context = this.service.values.get('log_context');
if ( ! log_context ) {
throw new Error('extension tried to access "log_context" before it was ' +
@@ -142,7 +142,7 @@ class Extension extends AdvancedBase {
}
return log_context;
}
get errors () {
return memoized_errors ?? (() => {
return this.services.get('error-service').create(this.log_context);
@@ -155,7 +155,7 @@ class Extension extends AdvancedBase {
* @param {string} [key] Key of data being registered
* @param {any} data The data to be registered
*/
register(typeKey, keyOrData, data) {
register (typeKey, keyOrData, data) {
if ( ! this.registry_[typeKey] ) {
this.registry_[typeKey] = {
named: {},
@@ -191,7 +191,7 @@ class Extension extends AdvancedBase {
* @param {string} [key] Key of data being registered
* @param {any} data The data to be registered
*/
reg(...a) {
reg (...a) {
this.register(...a);
}
@@ -201,7 +201,7 @@ class Extension extends AdvancedBase {
* @param {*} handler - function to handle the endpoint
* @param {*} options - options like noauth (bool) and mw (array)
*/
get(path, handler, options) {
get (path, handler, options) {
// this extension will have a default service
this.ensure_service_();
@@ -223,7 +223,7 @@ class Extension extends AdvancedBase {
* @param {*} handler - function to handle the endpoint
* @param {*} options - options like noauth (bool) and mw (array)
*/
post(path, handler, options) {
post (path, handler, options) {
// this extension will have a default service
this.ensure_service_();
@@ -245,7 +245,7 @@ class Extension extends AdvancedBase {
* @param {*} handler - function to handle the endpoint
* @param {*} options - options like noauth (bool) and mw (array)
*/
put(path, handler, options) {
put (path, handler, options) {
// this extension will have a default service
this.ensure_service_();
@@ -267,7 +267,7 @@ class Extension extends AdvancedBase {
* @param {*} options - options like noauth (bool) and mw (array)
*/
delete(path, handler, options) {
delete (path, handler, options) {
// this extension will have a default service
this.ensure_service_();
@@ -283,7 +283,7 @@ class Extension extends AdvancedBase {
});
}
use(...args) {
use (...args) {
this.ensure_service_();
this.service.expressThings_.push({
type: 'router',
@@ -291,12 +291,12 @@ class Extension extends AdvancedBase {
});
}
get preinit() {
return (function(callback) {
get preinit () {
return (function (callback) {
this.on('preinit', callback);
}).bind(this);
}
set preinit(callback) {
set preinit (callback) {
if ( this.only_one_preinit_fn === null ) {
this.on('preinit', (...a) => {
this.only_one_preinit_fn(...a);
@@ -309,12 +309,12 @@ class Extension extends AdvancedBase {
this.only_one_preinit_fn = callback;
}
get init() {
return (function(callback) {
get init () {
return (function (callback) {
this.on('init', callback);
}).bind(this);
}
set init(callback) {
set init (callback) {
if ( this.only_one_init_fn === null ) {
this.on('init', (...a) => {
this.only_one_init_fn(...a);
@@ -327,7 +327,7 @@ class Extension extends AdvancedBase {
this.only_one_init_fn = callback;
}
get console() {
get console () {
const extensionConsole = Object.create(console);
const logfn = level => (...a) => {
let svc_log;
@@ -368,7 +368,7 @@ class Extension extends AdvancedBase {
*
* @returns {void}
*/
ensure_service_() {
ensure_service_ () {
if ( this.service ) {
return;
}

View File

@@ -1,30 +1,30 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
*
* This file is part of Puter.
*
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { AdvancedBase } = require("@heyputer/putility");
const { AdvancedBase } = require('@heyputer/putility');
const uuid = require('uuid');
const { ExtensionService } = require("./ExtensionService");
const { ExtensionService } = require('./ExtensionService');
class ExtensionModule extends AdvancedBase {
async install (context) {
const services = context.get('services');
this.extension.name = this.extension.name ?? context.name;
this.extension.emit('install', { context, services });

View File

@@ -31,7 +31,7 @@ const { Actor } = require('./services/auth/Actor');
* future) to the default service.
*/
class ExtensionServiceState extends AdvancedBase {
constructor(...a) {
constructor (...a) {
super(...a);
this.extension = a[0].extension;
@@ -41,7 +41,7 @@ class ExtensionServiceState extends AdvancedBase {
// Values shared between the `extension` global and its service
this.values = new Context();
}
register_route_handler_(path, handler, options = {}) {
register_route_handler_ (path, handler, options = {}) {
// handler and options may be flipped
if ( typeof handler === 'object' ) {
[handler, options] = [options, handler];
@@ -77,10 +77,10 @@ class ExtensionServiceState extends AdvancedBase {
* provide a default service for extensions.
*/
class ExtensionService extends BaseService {
_construct() {
_construct () {
this.expressThings_ = [];
}
async _init(args) {
async _init (args) {
this.state = args.state;
this.state.values.set('services', this.services);
@@ -153,7 +153,7 @@ class ExtensionService extends BaseService {
this.state.extension.emit('preinit');
}
async ['__on_boot.consolidation'](...a) {
async ['__on_boot.consolidation'] (...a) {
const svc_su = this.services.get('su');
await svc_su.sudo(async () => {
await this.state.extension.emit('init', {}, {
@@ -161,7 +161,7 @@ class ExtensionService extends BaseService {
});
});
}
async ['__on_boot.activation'](...a) {
async ['__on_boot.activation'] (...a) {
const svc_su = this.services.get('su');
await svc_su.sudo(async () => {
await this.state.extension.emit('activate', {}, {
@@ -169,7 +169,7 @@ class ExtensionService extends BaseService {
});
});
}
async ['__on_boot.ready'](...a) {
async ['__on_boot.ready'] (...a) {
const svc_su = this.services.get('su');
await svc_su.sudo(async () => {
await this.state.extension.emit('ready', {}, {
@@ -178,7 +178,7 @@ class ExtensionService extends BaseService {
});
}
['__on_install.routes'](_, { app }) {
['__on_install.routes'] (_, { app }) {
if ( ! this.state ) debugger;
for ( const thing of this.state.expressThings_ ) {
if ( thing.type === 'endpoint' ) {

View File

@@ -16,25 +16,25 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { AdvancedBase, libs } = require("@heyputer/putility");
const { AdvancedBase, libs } = require('@heyputer/putility');
const { Context } = require('./util/context');
const BaseService = require("./services/BaseService");
const BaseService = require('./services/BaseService');
const useapi = require('useapi');
const yargs = require('yargs/yargs')
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const { Extension } = require("./Extension");
const { ExtensionModule } = require("./ExtensionModule");
const { spawn } = require("node:child_process");
const { Extension } = require('./Extension');
const { ExtensionModule } = require('./ExtensionModule');
const { spawn } = require('node:child_process');
const fs = require('fs');
const path_ = require('path');
const { prependToJSFiles } = require("./kernel/modutil");
const { prependToJSFiles } = require('./kernel/modutil');
const uuid = require('uuid');
const readline = require("node:readline/promises");
const { RuntimeModuleRegistry } = require("./extension/RuntimeModuleRegistry");
const { RuntimeModule } = require("./extension/RuntimeModule");
const deep_proto_merge = require("./config/deep_proto_merge");
const readline = require('node:readline/promises');
const { RuntimeModuleRegistry } = require('./extension/RuntimeModuleRegistry');
const { RuntimeModule } = require('./extension/RuntimeModule');
const deep_proto_merge = require('./config/deep_proto_merge');
const { quot } = libs.string;
@@ -54,7 +54,7 @@ class Kernel extends AdvancedBase {
this.extensionExports = {};
this.extensionInfo = {};
this.registry = {};
this.runtimeModuleRegistry = new RuntimeModuleRegistry();
}
@@ -90,17 +90,18 @@ class Kernel extends AdvancedBase {
}
boot () {
const args = yargs(hideBin(process.argv)).argv
const args = yargs(hideBin(process.argv)).argv;
this._runtime_init({ args });
const config = require('./config');
globalThis.ll = o => o;
globalThis.xtra_log = () => {};
globalThis.xtra_log = () => {
};
if ( config.env === 'dev' ) {
globalThis.ll = o => {
console.log('debug: ' + require('node:util').inspect(o));
console.log(`debug: ${ require('node:util').inspect(o)}`);
return o;
};
globalThis.xtra_log = (...args) => {
@@ -108,8 +109,8 @@ class Kernel extends AdvancedBase {
const fs = require('fs');
const path = require('path');
const log_path = path.join('/tmp/xtra_log.txt');
fs.appendFileSync(log_path, args.join(' ') + '\n');
}
fs.appendFileSync(log_path, `${args.join(' ') }\n`);
};
}
const { consoleLogManager } = require('./util/consolelog');
@@ -140,7 +141,6 @@ class Kernel extends AdvancedBase {
await this._boot_services();
});
Error.stackTraceLimit = 200;
}
@@ -157,7 +157,7 @@ class Kernel extends AdvancedBase {
});
await module_.install(mod_context);
}
for ( const k in services.instances_ ) {
const service_exports = new RuntimeModule({ name: `service:${k}` });
this.runtimeModuleRegistry.register(service_exports);
@@ -188,10 +188,8 @@ class Kernel extends AdvancedBase {
throw e;
}
await svc_systemValidation.mark_invalid(
'failed to initialize services',
e,
);
await svc_systemValidation.mark_invalid('failed to initialize services',
e);
}
for ( const module of this.modules ) {
@@ -228,24 +226,24 @@ class Kernel extends AdvancedBase {
}
async install_extern_mods_ () {
// In runtime directory, we'll create a `mod_packages` directory.`
if ( fs.existsSync('mod_packages') ) {
fs.rmSync('mod_packages', { recursive: true, force: true });
}
fs.mkdirSync('mod_packages');
// Initialize some globals that external mods depend on
globalThis.__puter_extension_globals__ = {
extensionObjectRegistry: {},
useapi: this.useapi,
global_config: require('./config'),
};
// Install the mods...
const mod_install_root_context = Context.get();
const mod_directory_promises = [];
const mod_installation_promises = [];
@@ -253,9 +251,7 @@ class Kernel extends AdvancedBase {
for ( const mods_dirpath of mod_paths ) {
const p = (async () => {
if ( ! fs.existsSync(mods_dirpath) ) {
this.services.logger.error(
`mod directory not found: ${quot(mods_dirpath)}; skipping...`
);
this.services.logger.error(`mod directory not found: ${quot(mods_dirpath)}; skipping...`);
// intentional delay so error is seen
this.services.logger.info('boot will continue in 4 seconds');
await new Promise(rslv => setTimeout(rslv, 4000));
@@ -266,7 +262,7 @@ class Kernel extends AdvancedBase {
const ignoreList = new Set([
'.git',
]);
for ( const mod_dirname of mod_dirnames ) {
if ( ignoreList.has(mod_dirname) ) continue;
mod_installation_promises.push(this.install_extern_mod_({
@@ -279,31 +275,31 @@ class Kernel extends AdvancedBase {
if ( process.env.SYNC_MOD_INSTALL ) await p;
mod_directory_promises.push(p);
}
await Promise.all(mod_directory_promises);
const mods_to_run = (await Promise.all(mod_installation_promises))
.filter(v => v !== undefined);
mods_to_run.sort((a, b) => a.priority - b.priority);
let i = 0;
while (i < mods_to_run.length) {
while ( i < mods_to_run.length ) {
const currentPriority = mods_to_run[i].priority;
const samePriorityMods = [];
// Collect all mods with the same priority
while (i < mods_to_run.length && mods_to_run[i].priority === currentPriority) {
while ( i < mods_to_run.length && mods_to_run[i].priority === currentPriority ) {
samePriorityMods.push(mods_to_run[i]);
i++;
}
// Run all mods with the same priority concurrently
await Promise.all(samePriorityMods.map(mod_entry => {
return this._run_extern_mod(mod_entry);
}));
}
}
async install_extern_mod_({
async install_extern_mod_ ({
mod_install_root_context,
mod_dirname,
mod_path,
@@ -315,19 +311,19 @@ class Kernel extends AdvancedBase {
}
// Mod must be a directory or javascript file
if ( ! stat.isDirectory() && !(mod_path.endsWith('.js')) ) {
if ( !stat.isDirectory() && !(mod_path.endsWith('.js')) ) {
return;
}
let mod_name = path_.parse(mod_path).name;
const mod_package_dir = `mod_packages/${mod_name}`;
fs.mkdirSync(mod_package_dir);
const mod_entry = {
priority: 0,
jsons: {},
};
if ( ! stat.isDirectory() ) {
const rl = readline.createInterface({
input: fs.createReadStream(mod_path),
@@ -340,7 +336,7 @@ class Kernel extends AdvancedBase {
mod_entry.priority = Number(tokens[2]);
}
if ( tokens[1] === 'name' ) {
mod_name = '' + tokens[2];
mod_name = `${ tokens[2]}`;
}
}
mod_entry.jsons.package = await this.create_mod_package_json(mod_package_dir, {
@@ -354,7 +350,7 @@ class Kernel extends AdvancedBase {
this.bootLogger.warn(`Empty mod directory ${quot(mod_path)}; skipping...`);
return;
}
const promises = [];
// Create package.json if it doesn't exist
@@ -369,7 +365,7 @@ class Kernel extends AdvancedBase {
mod_entry.jsons.package = JSON.parse(str);
}
})());
const puter_json_path = path_.join(mod_path, 'puter.json');
if ( fs.existsSync(puter_json_path) ) {
promises.push((async () => {
@@ -391,45 +387,45 @@ class Kernel extends AdvancedBase {
mod_entry.jsons.config = obj;
})());
}
// Copy mod contents to `/mod_packages`
promises.push(fs.promises.cp(mod_path, mod_package_dir, {
recursive: true,
}));
await Promise.all(promises);
}
mod_entry.priority = mod_entry.jsons.puter?.priority ?? mod_entry.priority;
const extension_id = uuid.v4();
await prependToJSFiles(mod_package_dir, [
`const { use, def } = globalThis.__puter_extension_globals__.useapi;`,
`const { use: puter } = globalThis.__puter_extension_globals__.useapi;`,
`const extension = globalThis.__puter_extension_globals__` +
`.extensionObjectRegistry[${JSON.stringify(extension_id)}];`,
`const console = extension.console;`,
`const runtime = extension.runtime;`,
`const config = extension.config;`,
`const registry = extension.registry;`,
`const register = registry.register;`,
`const global_config = globalThis.__puter_extension_globals__.global_config`,
].join('\n') + '\n');
await prependToJSFiles(mod_package_dir, `${[
'const { use, def } = globalThis.__puter_extension_globals__.useapi;',
'const { use: puter } = globalThis.__puter_extension_globals__.useapi;',
'const extension = globalThis.__puter_extension_globals__' +
`.extensionObjectRegistry[${JSON.stringify(extension_id)}];`,
'const console = extension.console;',
'const runtime = extension.runtime;',
'const config = extension.config;',
'const registry = extension.registry;',
'const register = registry.register;',
'const global_config = globalThis.__puter_extension_globals__.global_config',
].join('\n') }\n`);
mod_entry.require_dir = path_.join(process.cwd(), mod_package_dir);
await this.run_npm_install(mod_entry.require_dir);
const mod = new ExtensionModule();
mod.extension = new Extension();
const runtimeModule = new RuntimeModule({ name: mod_name });
this.runtimeModuleRegistry.register(runtimeModule);
mod.extension.runtime = runtimeModule;
mod_entry.module = mod;
globalThis.__puter_extension_globals__.extensionObjectRegistry[extension_id]
= mod.extension;
@@ -439,23 +435,23 @@ class Kernel extends AdvancedBase {
external: true,
mod_path,
});
mod_entry.context = mod_context;
return mod_entry;
};
async _run_extern_mod(mod_entry) {
async _run_extern_mod (mod_entry) {
let exportObject = null;
const {
module: mod,
require_dir,
context,
} = mod_entry;
const packageJSON = mod_entry.jsons.package;
Object.defineProperty(mod.extension, 'config', {
get: () => {
const builtin_config = mod_entry.jsons.config ?? {};
@@ -463,9 +459,9 @@ class Kernel extends AdvancedBase {
return deep_proto_merge(user_config, builtin_config);
},
});
mod.extension.name = packageJSON.name;
const maybe_promise = (typ => typ.trim().toLowerCase())(packageJSON.type ?? '') === 'module'
? await import(path_.join(require_dir, packageJSON.main ?? 'index.js'))
: require(require_dir);
@@ -473,7 +469,7 @@ class Kernel extends AdvancedBase {
if ( maybe_promise && maybe_promise instanceof Promise ) {
exportObject = await maybe_promise;
} else exportObject = maybe_promise;
const extension_name = exportObject?.name ?? packageJSON.name;
this.extensionExports[extension_name] = exportObject;
this.extensionInfo[extension_name] = {
@@ -483,7 +479,7 @@ class Kernel extends AdvancedBase {
};
mod.extension.registry = this.registry;
mod.extension.name = extension_name;
if ( exportObject.construct ) {
mod.extension.on('construct', exportObject.construct);
}
@@ -503,7 +499,7 @@ class Kernel extends AdvancedBase {
const modapi = {};
let mod_path = options.mod_path;
if ( ! mod_path && options.module.dirname ) {
if ( !mod_path && options.module.dirname ) {
mod_path = options.module.dirname();
}
@@ -531,13 +527,13 @@ class Kernel extends AdvancedBase {
// getter-like behavior to useapi.
this.useapi.def(`${prefix}.${name}`, lib);
}
}
};
}
const mod_context = parent.sub({ modapi }, `mod:${options.name}`);
return mod_context;
}
async create_mod_package_json (mod_path, { name, entry }) {
// Expect main.js or index.js to exist
const options = ['main.js', 'index.js'];
@@ -556,7 +552,7 @@ class Kernel extends AdvancedBase {
if ( ! entry ) {
this.bootLogger.error(`Expected main.js or index.js in ${quot(mod_path)}`);
if ( ! process.env.SKIP_INVALID_MODS ) {
this.bootLogger.error(`Set SKIP_INVALID_MODS=1 (environment variable) to run anyway.`);
this.bootLogger.error('Set SKIP_INVALID_MODS=1 (environment variable) to run anyway.');
process.exit(1);
} else {
return;
@@ -569,16 +565,16 @@ class Kernel extends AdvancedBase {
main: entry ?? 'main.js',
};
const data_json = JSON.stringify(data);
this.bootLogger.debug('WRITING TO: ' + path_.join(mod_path, 'package.json'));
this.bootLogger.debug(`WRITING TO: ${ path_.join(mod_path, 'package.json')}`);
await fs.promises.writeFile(path_.join(mod_path, 'package.json'), data_json);
return data;
}
async run_npm_install (path) {
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
const proc = spawn(npmCmd, ["install"], { cwd: path, stdio: "pipe" });
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const proc = spawn(npmCmd, ['install'], { cwd: path, stdio: 'pipe' });
let buffer = '';

View File

@@ -16,12 +16,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { AdvancedBase } = require("@heyputer/putility");
const { AdvancedBase } = require('@heyputer/putility');
class LocalDiskStorageModule extends AdvancedBase {
async install (context) {
const services = context.get('services');
const LocalDiskStorageService = require("./services/LocalDiskStorageService");
const LocalDiskStorageService = require('./services/LocalDiskStorageService');
services.registerService('local-disk-storage', LocalDiskStorageService);
const HostDiskUsageService = require('./services/HostDiskUsageService');

View File

@@ -19,7 +19,7 @@
class MemoryStorageModule {
async install (context) {
const services = context.get('services');
const MemoryStorageService = require("./services/MemoryStorageService");
const MemoryStorageService = require('./services/MemoryStorageService');
services.registerService('memory-storage', MemoryStorageService);
}
}

View File

@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { AdvancedBase } = require("@heyputer/putility");
const { AdvancedBase } = require('@heyputer/putility');
class ThirdPartyDriversModule extends AdvancedBase {
// constructor () {

View File

@@ -6,14 +6,14 @@
// These annotated classes provide a solution to wrap plain objects.
class AnnotatedObject {
constructor (o) {
for ( const k in o ) this[k] = o[k];
}
}
class object_returned_by_get_app extends AnnotatedObject {};
class object_returned_by_get_app extends AnnotatedObject {
};
module.exports = {
object_returned_by_get_app,

View File

@@ -23,7 +23,7 @@ const _path = require('path');
* PathOrUIDValidator validates that either `path` or `uid` is present
* in the request and requires a valid value for the parameter that was
* used. Additionally, resolves the path if a path was provided.
*
*
* @class PathOrUIDValidator
* @static
* @throws {APIError} if `path` and `uid` are both missing
@@ -37,20 +37,30 @@ module.exports = class PathOrUIDValidator {
const params = req.method === 'GET'
? req.query : req.body ;
if(!params.path && !params.uid)
if ( !params.path && !params.uid )
{
throw new APIError(400, '`path` or `uid` must be provided.');
}
// `path` must be a string
else if (params.path && !params.uid && typeof params.path !== 'string')
else if ( params.path && !params.uid && typeof params.path !== 'string' )
{
throw new APIError(400, '`path` must be a string.');
}
// `path` cannot be empty
else if(params.path && !params.uid && params.path.trim() === '')
else if ( params.path && !params.uid && params.path.trim() === '' )
{
throw new APIError(400, '`path` cannot be empty');
}
// `uid` must be a valid uuid
else if(params.uid && !params.path && !require('uuid').validate(params.uid))
else if ( params.uid && !params.path && !require('uuid').validate(params.uid) )
{
throw new APIError(400, '`uid` must be a valid uuid');
}
// resolve path if provided
if(params.path)
if ( params.path )
{
params.path = _path.resolve('/', params.path);
}
}
};

View File

@@ -16,63 +16,63 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("./APIError");
const APIError = require('./APIError');
/**
* api_error_handler() is an express error handler for API errors.
* It adheres to the express error handler signature and should be
* used as the last middleware in an express app.
*
*
* Since Express 5 is not yet released, this function is used by
* eggspress() to handle errors instead of as a middleware.
*
*
* @todo remove this function and use express error handling
* when Express 5 is released
*
* @param {*} err
* @param {*} req
* @param {*} res
* @param {*} next
* @returns
*
* @param {*} err
* @param {*} req
* @param {*} res
* @param {*} next
* @returns
*/
module.exports = function (err, req, res, next) {
if (res.headersSent) {
console.error('error after headers were sent:', err);
return next(err)
}
if ( res.headersSent ) {
console.error('error after headers were sent:', err);
return next(err);
}
// API errors might have a response to help the
// developer resolve the issue.
if ( err instanceof APIError ) {
return err.write(res);
}
// API errors might have a response to help the
// developer resolve the issue.
if ( err instanceof APIError ) {
return err.write(res);
}
if (
typeof err === 'object' &&
! (err instanceof Error) &&
err.hasOwnProperty('message')
) {
const apiError = APIError.create(400, err);
return apiError.write(res);
}
if (
typeof err === 'object' &&
!(err instanceof Error) &&
err.hasOwnProperty('message')
) {
const apiError = APIError.create(400, err);
return apiError.write(res);
}
console.error('internal server error:', err);
console.error('internal server error:', err);
const services = globalThis.services;
if ( services && services.has('alarm') ) {
const alarm = services.get('alarm');
alarm.create('api_error_handler', err.message, {
error: err,
url: req.url,
method: req.method,
body: req.body,
headers: req.headers,
});
}
const services = globalThis.services;
if ( services && services.has('alarm') ) {
const alarm = services.get('alarm');
alarm.create('api_error_handler', err.message, {
error: err,
url: req.url,
method: req.method,
body: req.body,
headers: req.headers,
});
}
req.__error_handled = true;
// Other errors should provide as little information
// to the client as possible for security reasons.
return res.send(500, 'Internal Server Error');
req.__error_handled = true;
// Other errors should provide as little information
// to the client as possible for security reasons.
return res.send(500, 'Internal Server Error');
};

View File

@@ -1,18 +1,18 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
*
* This file is part of Puter.
*
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

View File

@@ -16,11 +16,11 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { is_valid_path } = require("../../filesystem/validation");
const { is_valid_uuid4 } = require("../../helpers");
const { Context } = require("../../util/context");
const { PathBuilder } = require("../../util/pathutil");
const APIError = require("../APIError");
const { is_valid_path } = require('../../filesystem/validation');
const { is_valid_uuid4 } = require('../../helpers');
const { Context } = require('../../util/context');
const { PathBuilder } = require('../../util/pathutil');
const APIError = require('../APIError');
const _path = require('path');
module.exports = class FSNodeParam {
@@ -49,12 +49,12 @@ module.exports = class FSNodeParam {
});
}
if ( ! ['/','.','~'].includes(uidOrPath[0]) ) {
if ( ! ['/', '.', '~'].includes(uidOrPath[0]) ) {
if ( is_valid_uuid4(uidOrPath) ) {
return await fs.node({ uid: uidOrPath });
}
log.debug('tried uuid', { uidOrPath })
log.debug('tried uuid', { uidOrPath });
throw APIError.create('field_invalid', null, {
key: this.srckey,
expected: 'unix-style path or uuid4',
@@ -67,7 +67,7 @@ module.exports = class FSNodeParam {
}
if ( ! is_valid_path(uidOrPath) ) {
log.debug('tried path', { uidOrPath })
log.debug('tried path', { uidOrPath });
throw APIError.create('field_invalid', null, {
key: this.srckey,
expected: 'unix-style path or uuid4',
@@ -77,4 +77,4 @@ module.exports = class FSNodeParam {
const resolved_path = PathBuilder.resolve(uidOrPath, { puterfs: true });
return await fs.node({ path: resolved_path });
}
}
};

View File

@@ -56,10 +56,10 @@ module.exports = class FlagParam {
return value;
}
log.debug('tried boolean', { value })
log.debug('tried boolean', { value });
throw APIError.create('field_invalid', null, {
key: this.srckey,
expected: 'boolean',
});
}
}
};

View File

@@ -44,7 +44,7 @@ module.exports = class StringParam {
}
if ( typeof value !== 'string' ) {
log.debug('tried string', { value })
log.debug('tried string', { value });
throw APIError.create('field_invalid', null, {
key: this.srckey,
expected: 'string',
@@ -53,4 +53,4 @@ module.exports = class StringParam {
return value;
}
}
};

View File

@@ -20,4 +20,4 @@ module.exports = class UserParam {
consolidate ({ req }) {
return req.user;
}
}
};

View File

@@ -17,4 +17,4 @@ require('dotenv').config();
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
"use strict"
'use strict';

View File

@@ -18,26 +18,20 @@
*/
class BootLogger {
info (...args) {
console.log(
'\x1B[36;1m[BOOT/INFO]\x1B[0m',
...args,
);
console.log('\x1B[36;1m[BOOT/INFO]\x1B[0m',
...args);
}
debug (...args) {
if ( ! process.env.DEBUG ) return;
console.log('\x1B[37m[BOOT/DEBUG]', ...args, '\x1B[0m');
}
error (...args) {
console.log(
'\x1B[31;1m[BOOT/ERROR]\x1B[0m',
...args,
);
console.log('\x1B[31;1m[BOOT/ERROR]\x1B[0m',
...args);
}
warn (...args) {
console.log(
'\x1B[33;1m[BOOT/WARN]\x1B[0m',
...args,
);
console.log('\x1B[33;1m[BOOT/WARN]\x1B[0m',
...args);
}
}

View File

@@ -16,13 +16,13 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { AdvancedBase } = require("@heyputer/putility");
const { AdvancedBase } = require('@heyputer/putility');
const { quot } = require('@heyputer/putility').libs.string;
const { TechnicalError } = require("../errors/TechnicalError");
const { print_error_help } = require("../errors/error_help_details");
const default_config = require("./default_config");
const config = require("../config");
const { ConfigLoader } = require("../config/ConfigLoader");
const { TechnicalError } = require('../errors/TechnicalError');
const { print_error_help } = require('../errors/error_help_details');
const default_config = require('./default_config');
const config = require('../config');
const { ConfigLoader } = require('../config/ConfigLoader');
// highlights a string
const hl = s => `\x1b[33;1m${s}\x1b[0m`;
@@ -48,7 +48,7 @@ const path_checks = ({ logger }) => ({ fs, path_ }) => ({
if ( path == undefined ) return false;
const exists = fs.existsSync(path);
if ( !exists ) {
if ( ! exists ) {
throw new Error(`Path does not exist: ${path}`);
}
@@ -93,8 +93,8 @@ const path_checks = ({ logger }) => ({ fs, path_ }) => ({
throw new Error(`No valid config file found in path: ${path}`);
},
env_not_set: name => () => {
return ! process.env[name];
}
return !process.env[name];
},
});
// Configuration paths in order of precedence.
@@ -102,7 +102,9 @@ const path_checks = ({ logger }) => ({ fs, path_ }) => ({
const config_paths = ({ path_checks }) => ({ path_ }) => [
{
label: '$CONFIG_PATH',
get path () { return process.env.CONFIG_PATH },
get path () {
return process.env.CONFIG_PATH;
},
checks: [
path_checks.require_if_not_undefined,
],
@@ -135,7 +137,9 @@ const valid_config_names = [
const runtime_paths = ({ path_checks }) => ({ path_ }) => [
{
label: '$RUNTIME_PATH',
get path () { return process.env.RUNTIME_PATH },
get path () {
return process.env.RUNTIME_PATH;
},
checks: [
path_checks.require_if_not_undefined,
],
@@ -165,7 +169,9 @@ const runtime_paths = ({ path_checks }) => ({ path_ }) => [
const mod_paths = ({ path_checks, entry_path }) => ({ path_ }) => [
{
label: '$MOD_PATH',
get path () { return process.env.MOD_PATH },
get path () {
return process.env.MOD_PATH;
},
checks: [
path_checks.require_if_not_undefined,
],
@@ -179,8 +185,7 @@ const mod_paths = ({ path_checks, entry_path }) => ({ path_ }) => [
},
{
get path () {
return path_.join(path_.dirname(
entry_path || require.main.filename), '../mods');
return path_.join(path_.dirname(entry_path || require.main.filename), '../mods');
},
checks: [ path_checks.skip_if_not_exists ],
},
@@ -192,7 +197,7 @@ class RuntimeEnvironment extends AdvancedBase {
path_: require('node:path'),
crypto: require('node:crypto'),
format: require('string-template'),
}
};
constructor ({ logger, entry_path, boot_parameters }) {
super();
@@ -220,35 +225,28 @@ class RuntimeEnvironment extends AdvancedBase {
// with some helpful values. A partial-population of this object later
// in this function will be used when evaluating configured paths.
const environment = {};
environment.source = this.modules.path_.dirname(
this.entry_path || require.main.filename);
environment.source = this.modules.path_.dirname(this.entry_path || require.main.filename);
environment.repo = this.modules.path_.dirname(environment.source);
const config_path_entry = this.get_first_suitable_path_(
{ pathFor: 'configuration' },
this.config_paths,
[
this.path_checks.require_read_permission,
// this.path_checks.contains_config_file,
]
);
const config_path_entry = this.get_first_suitable_path_({ pathFor: 'configuration' },
this.config_paths,
[
this.path_checks.require_read_permission,
// this.path_checks.contains_config_file,
]);
// Note: there used to be a 'mods_path_entry' here too
// but it was never used
const pwd_path_entry = this.get_first_suitable_path_(
{ pathFor: 'working directory' },
this.runtime_paths,
[ this.path_checks.require_write_permission ]
);
const pwd_path_entry = this.get_first_suitable_path_({ pathFor: 'working directory' },
this.runtime_paths,
[ this.path_checks.require_write_permission ]);
process.chdir(pwd_path_entry.path);
// Check for a valid config file in the config path
let using_config;
for ( const name of valid_config_names ) {
const exists = this.modules.fs.existsSync(
this.modules.path_.join(config_path_entry.path, name)
);
const exists = this.modules.fs.existsSync(this.modules.path_.join(config_path_entry.path, name));
if ( exists ) {
using_config = name;
break;
@@ -266,21 +264,15 @@ class RuntimeEnvironment extends AdvancedBase {
generated_values.private_uid_secret = crypto.randomBytes(24).toString('hex');
generated_values.private_uid_namespace = crypto.randomUUID();
if ( using_config ) {
this.logger.debug(
`Overwriting ${quot(using_config)} because ` +
`${hl('--overwrite-config')} is set`
);
this.logger.debug(`Overwriting ${quot(using_config)} because ` +
`${hl('--overwrite-config')} is set`);
// make backup
fs.copyFileSync(
path_.join(config_path_entry.path, using_config),
path_.join(config_path_entry.path, using_config + '.bak'),
);
fs.copyFileSync(path_.join(config_path_entry.path, using_config),
path_.join(config_path_entry.path, `${using_config }.bak`));
// preserve generated values
{
const config_raw = fs.readFileSync(
path_.join(config_path_entry.path, using_config),
'utf8',
);
const config_raw = fs.readFileSync(path_.join(config_path_entry.path, using_config),
'utf8');
const config_values = JSON.parse(config_raw);
for ( const k in generated_values ) {
if ( ! config_values[k] ) continue;
@@ -292,33 +284,25 @@ class RuntimeEnvironment extends AdvancedBase {
...default_config,
...generated_values,
};
generated_config[""] = null; // for trailing comma
fs.writeFileSync(
path_.join(config_path_entry.path, 'config.json'),
JSON.stringify(generated_config, null, 4) + '\n',
);
generated_config[''] = null; // for trailing comma
fs.writeFileSync(path_.join(config_path_entry.path, 'config.json'),
`${JSON.stringify(generated_config, null, 4) }\n`);
using_config = 'config.json';
}
let config_to_load = 'config.json';
if ( process.env.PUTER_CONFIG_PROFILE ) {
this.logger.debug(
hl('PROFILE') + ' ' +
quot(process.env.PUTER_CONFIG_PROFILE) + ' ' +
`because $PUTER_CONFIG_PROFILE is set`
);
config_to_load = `${process.env.PUTER_CONFIG_PROFILE}.json`
const exists = fs.existsSync(
path_.join(config_path_entry.path, config_to_load)
);
this.logger.debug(`${hl('PROFILE') } ${
quot(process.env.PUTER_CONFIG_PROFILE) } ` +
'because $PUTER_CONFIG_PROFILE is set');
config_to_load = `${process.env.PUTER_CONFIG_PROFILE}.json`;
const exists = fs.existsSync(path_.join(config_path_entry.path, config_to_load));
if ( ! exists ) {
fs.writeFileSync(
path_.join(config_path_entry.path, config_to_load),
JSON.stringify({
config_name: process.env.PUTER_CONFIG_PROFILE,
$imports: ['config.json'],
}, null, 4) + '\n',
);
fs.writeFileSync(path_.join(config_path_entry.path, config_to_load),
`${JSON.stringify({
config_name: process.env.PUTER_CONFIG_PROFILE,
$imports: ['config.json'],
}, null, 4) }\n`);
}
}
@@ -330,7 +314,7 @@ class RuntimeEnvironment extends AdvancedBase {
if ( ! config.config_name ) {
throw new Error('config_name is required');
}
this.logger.debug(hl(`config name`) + ` ${quot(config.config_name)}`);
this.logger.debug(`${hl('config name') } ${quot(config.config_name)}`);
const mod_paths = [];
environment.mod_paths = mod_paths;
@@ -346,9 +330,7 @@ class RuntimeEnvironment extends AdvancedBase {
// If configured, add a user-specified mod path
if ( config.mod_directories ) {
for ( const dir of config.mod_directories ) {
const mods_directory = this.modules.format(
dir, environment,
);
const mods_directory = this.modules.format(dir, environment);
mod_paths.push(mods_directory);
}
}
@@ -359,30 +341,22 @@ class RuntimeEnvironment extends AdvancedBase {
get_first_suitable_path_ (meta, paths, last_checks) {
for ( const entry of paths ) {
const checks = [...(entry.checks ?? []), ...last_checks];
this.logger.debug(
`Checking path ${quot(entry.label ?? entry.path)} for ${meta.pathFor}...`
);
this.logger.debug(`Checking path ${quot(entry.label ?? entry.path)} for ${meta.pathFor}...`);
let checks_pass = true;
for ( const check of checks ) {
this.logger.debug(
`-> doing ${quot(check.name)} on path ${quot(entry.path)}...`
);
this.logger.debug(`-> doing ${quot(check.name)} on path ${quot(entry.path)}...`);
const result = check(entry);
if ( result === false ) {
this.logger.debug(
`-> ${quot(check.name)} doesn't like this path`
);
this.logger.debug(`-> ${quot(check.name)} doesn't like this path`);
checks_pass = false;
break;
}
}
if ( ! checks_pass ) continue;
this.logger.info(
`${hl(meta.pathFor)} ${quot(entry.path)}`
)
this.logger.info(`${hl(meta.pathFor)} ${quot(entry.path)}`);
return entry;
}

View File

@@ -32,7 +32,7 @@ module.exports = {
path: 'puter-database.sqlite',
},
thumbnails: {
engine: 'purejs'
engine: 'purejs',
},
'file-cache': {
disk_limit: 16384,
@@ -40,6 +40,6 @@ module.exports = {
precache_size: 16384,
path: './file-cache',
}
},
},
};

View File

@@ -36,7 +36,7 @@ class CodeUtil {
async _run () {
return await method.call(this.self, this.values);
}
}
};
Object.defineProperty(cls, 'name', { value: cls_name });
@@ -44,7 +44,7 @@ class CodeUtil {
const op = new cls();
op.self = this;
return await op.run(...a);
}
};
}
}

View File

@@ -105,7 +105,7 @@
* Supports conditional steps, deferred steps, and can be used as a runnable implementation for classes.
* @class @extends Function
*/
class Sequence {
class Sequence {
/**
* SequenceState represents the state of a Sequence execution.
* Provides access to the sequence scope, step control, and utility methods for step functions.
@@ -116,7 +116,7 @@ class Sequence {
* @param {Sequence|function} sequence - The Sequence instance or its callable function.
* @param {Object} [thisArg] - The instance to bind as `this` for step functions.
*/
constructor(sequence, thisArg) {
constructor (sequence, thisArg) {
if ( typeof sequence === 'function' ) {
sequence = sequence.sequence;
}
@@ -138,7 +138,7 @@ class Sequence {
* Get the current steps array for this sequence execution.
* @returns {Array<function|Object>} The steps to execute.
*/
get steps() {
get steps () {
return this.steps_ ?? this.sequence_?.steps_;
}
@@ -147,7 +147,7 @@ class Sequence {
* @param {Object} [values] - Initial values for the sequence scope.
* @returns {Promise<void>}
*/
async run(values) {
async run (values) {
// Initialize scope
values = values || this.thisArg?.values || {};
Object.setPrototypeOf(this.scope_, values);
@@ -162,7 +162,7 @@ class Sequence {
};
}
if ( step.condition && ! await step.condition(this) ) {
if ( step.condition && !await step.condition(this) ) {
continue;
}
@@ -203,7 +203,7 @@ class Sequence {
* The first time defer is called, clones the steps and sets up for deferred insertion.
* @param {function(Sequence.SequenceState): Promise<any>} fn - The function to defer.
*/
static defer_0 = function(fn) {
static defer_0 = function (fn) {
this.steps_ = [...this.sequence_.steps_];
this.defer = this.constructor.defer_1;
this.defer_ptr_ = this.steps_.length;
@@ -213,7 +213,7 @@ class Sequence {
* Subsequent calls to defer insert the function before the deferred pointer.
* @param {function(Sequence.SequenceState): Promise<any>} fn - The function to defer.
*/
static defer_1 = function(fn) {
static defer_1 = function (fn) {
// Deferred functions don't affect the return value
const real_fn = fn;
fn = async () => {
@@ -230,7 +230,7 @@ class Sequence {
* @param {string} k - The key to retrieve.
* @returns {any} The value associated with the key.
*/
get(k) {
get (k) {
// TODO: record read1
return this.scope_[k];
}
@@ -240,7 +240,7 @@ class Sequence {
* @param {string} k - The key to set.
* @param {any} v - The value to assign.
*/
set(k, v) {
set (k, v) {
// TODO: record mutation
this.scope_[k] = v;
}
@@ -250,7 +250,7 @@ class Sequence {
* @param {Object} [opt_itemsToSet] - Optional object of key-value pairs to set.
* @returns {Object} Proxy to the current scope for value access.
*/
values(opt_itemsToSet) {
values (opt_itemsToSet) {
if ( opt_itemsToSet ) {
for ( const k in opt_itemsToSet ) {
this.set(k, opt_itemsToSet[k]);
@@ -273,7 +273,7 @@ class Sequence {
* @param {string} [k] - The property name to retrieve. If omitted, returns the instance.
* @returns {any} The value from the instance or the instance itself.
*/
iget(k) {
iget (k) {
if ( k === undefined ) return this.thisArg;
return this.thisArg?.[k];
}
@@ -285,7 +285,7 @@ class Sequence {
* @param {...any} args - Arguments to pass to the method.
* @returns {any} The result of the method call.
*/
icall(k, ...args) {
icall (k, ...args) {
return this.thisArg?.[k]?.call(this.thisArg, ...args);
}
@@ -297,7 +297,7 @@ class Sequence {
* @param {...any} args - Arguments to pass after the sequence state.
* @returns {any} The result of the method call.
*/
idcall(k, ...args) {
idcall (k, ...args) {
return this.thisArg?.[k]?.call(this.thisArg, this, ...args);
}
@@ -305,7 +305,7 @@ class Sequence {
* Get the logger from the instance, if available.
* @returns {Object|undefined} The logger object.
*/
get log() {
get log () {
return this.iget('log');
}
@@ -314,7 +314,7 @@ class Sequence {
* @param {any} [return_value] - Value to return from the sequence.
* @returns {any} The provided return value.
*/
stop(return_value) {
stop (return_value) {
this.stopped_ = true;
return return_value;
}
@@ -333,7 +333,7 @@ class Sequence {
* - Options object may include `name`, `record_history`, `before_each`, `after_each`.
* @returns {SequenceCallable} A callable function that runs the sequence.
*/
constructor(...args) {
constructor (...args) {
const sequence = this;
const steps = [];
@@ -356,7 +356,7 @@ class Sequence {
* @param {Object|Sequence.SequenceState} [opt_values] - Initial values or a SequenceState.
* @returns {Promise<any>} The return value of the last step.
*/
const fn = async function(opt_values) {
const fn = async function (opt_values) {
if ( opt_values && opt_values instanceof Sequence.SequenceState ) {
opt_values = opt_values.scope_;
}

View File

@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
"use strict"
'use strict';
const deep_proto_merge = require('./config/deep_proto_merge');
// const reserved_words = require('./config/reserved_words');
@@ -53,9 +53,9 @@ config.kv_max_value_size = 400 * 1024;
// Captcha configuration
config.captcha = {
enabled: false, // Enable captcha by default
enabled: false, // Enable captcha by default
expirationTime: 10 * 60 * 1000, // 10 minutes default expiration time
difficulty: 'medium' // Default difficulty level
difficulty: 'medium', // Default difficulty level
};
config.monitor = {
@@ -64,7 +64,7 @@ config.monitor = {
};
config.max_subdomains_per_user = 2000;
config.storage_capacity = 1*1024*1024*1024;
config.storage_capacity = 1 * 1024 * 1024 * 1024;
config.static_hosting_domain = 'site.puter.localhost';
// Storage limiting is set to false by default
@@ -74,11 +74,11 @@ config.available_device_storage = null;
config.thumb_width = 80;
config.thumb_height = 80;
config.app_max_icon_size = 5*1024*1024;
config.app_max_icon_size = 5 * 1024 * 1024;
config.defaultjs_asset_path = '../../';
config.short_description = `Puter is a privacy-first personal cloud that houses all your files, apps, and games in one private and secure place, accessible from anywhere at any time.`;
config.short_description = 'Puter is a privacy-first personal cloud that houses all your files, apps, and games in one private and secure place, accessible from anywhere at any time.';
config.title = 'Puter';
config.company = 'Puter Technologies Inc.';
@@ -103,18 +103,18 @@ config.reserved_words = [];
}
// set default S3 settings for this server, if any
if (config.server_id) {
// see if this server has a specific bucket
if ( config.server_id ) {
// see if this server has a specific bucket
for ( const server of config.servers ) {
if ( server.id !== config.server_id ) continue;
if ( ! server.s3_bucket ) continue;
config.s3_bucket = server.s3_bucket;
config.s3_region = server.region;
}
}
}
config.contact_email = 'hey@' + config.domain;
config.contact_email = `hey@${ config.domain}`;
// TODO: default value will be changed to false in a future release;
// details to follow in a future announcement.
@@ -154,14 +154,14 @@ module.exports = config;
// NEW_CONFIG_LOADING
const maybe_port = config =>
config.pub_port !== 80 && config.pub_port !== 443 ? ':' + config.pub_port : '';
config.pub_port !== 80 && config.pub_port !== 443 ? `:${ config.pub_port}` : '';
const computed_defaults = {
pub_port: config => config.http_port,
origin: config => config.protocol + '://' + config.domain + maybe_port(config),
origin: config => `${config.protocol }://${ config.domain }${maybe_port(config)}`,
api_base_url: config => config.experimental_no_subdomain
? config.origin
: config.protocol + '://api.' + config.domain + maybe_port(config),
: `${config.protocol }://api.${ config.domain }${maybe_port(config)}`,
social_card: config => `${config.origin}/assets/img/screenshot.png`,
};
@@ -186,7 +186,7 @@ const config_pointer = {};
};
replacement_config = deep_proto_merge(replacement_config, Object.getPrototypeOf(config_pointer), {
preserve_flag: true,
})
});
Object.setPrototypeOf(config_pointer, replacement_config);
};
@@ -198,37 +198,37 @@ const config_pointer = {};
// We have some values with computed defaults
{
const get_implied = (target, prop) => {
if (prop in computed_defaults) {
if ( prop in computed_defaults ) {
return computed_defaults[prop](target);
}
return undefined;
};
config_to_export = new Proxy(config_to_export, {
get: (target, prop, receiver) => {
if (prop in target) {
if ( prop in target ) {
return target[prop];
} else {
return get_implied(config_to_export, prop);
}
}
})
},
});
}
// We'd like to store values changed at runtime separately
// for easier runtime debugging
{
const config_runtime_values = {
$: 'runtime-values'
$: 'runtime-values',
};
let initialPrototype = config_to_export;
Object.setPrototypeOf(config_runtime_values, config_to_export);
config_to_export = config_runtime_values
config_to_export = config_runtime_values;
config_to_export.__set_config_object__ = (object, options = {}) => {
// options for this method
const replacePrototype = options.replacePrototype ?? true;
const useInitialPrototype = options.useInitialPrototype ?? true;
// maybe replace prototype
if ( replacePrototype ) {
const newProto = useInitialPrototype
@@ -236,7 +236,7 @@ const config_pointer = {};
: Object.getPrototypeOf(config_runtime_values);
Object.setPrototypeOf(object, newProto);
}
// use this object as the prototype
Object.setPrototypeOf(config_runtime_values, object);
};
@@ -247,14 +247,14 @@ const config_pointer = {};
set: (target, prop, value, receiver) => {
const logger = Context.get('logger', { allow_fallback: true });
// If no logger, just give up
if ( logger ) logger.debug(
'\x1B[36;1mCONFIGURATION MUTATED AT RUNTIME\x1B[0m',
{ prop, value },
);
if ( logger ) {
logger.debug('\x1B[36;1mCONFIGURATION MUTATED AT RUNTIME\x1B[0m',
{ prop, value });
}
target[prop] = value;
return true;
}
})
},
});
}
// We configure the behavior in context.js from here to avoid a cyclic

View File

@@ -16,14 +16,14 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { AdvancedBase } = require("@heyputer/putility");
const { AdvancedBase } = require('@heyputer/putility');
const { quot } = require('@heyputer/putility').libs.string;
class ConfigLoader extends AdvancedBase {
static MODULES = {
path_: require("path"),
fs: require("fs"),
}
path_: require('path'),
fs: require('fs'),
};
constructor (logger, path, config) {
super();
@@ -47,10 +47,8 @@ class ConfigLoader extends AdvancedBase {
delete config_values.$requires;
this.apply_requires(this.path, config_list, { by: name });
}
this.logger.debug(
`Applying config: ${path_.relative(this.path, config_path)}` +
(meta.by ? ` (required by ${meta.by})` : '')
);
this.logger.debug(`Applying config: ${path_.relative(this.path, config_path)}${
meta.by ? ` (required by ${meta.by})` : ''}`);
this.config.load_config(config_values);
}

View File

@@ -39,15 +39,13 @@ const deep_proto_merge = (replacement, delegate, options) => {
for ( const key in replacement ) {
if ( ! is_object(replacement[key]) ) continue;
if ( options?.preserve_flag && ! replacement[key].$preserve ) {
if ( options?.preserve_flag && !replacement[key].$preserve ) {
continue;
}
if ( ! is_object(delegate[key]) ) {
continue;
}
replacement[key] = deep_proto_merge(
replacement[key], delegate[key], options,
);
replacement[key] = deep_proto_merge(replacement[key], delegate[key], options);
}
// use a Proxy object to ensure all keys are present
@@ -62,10 +60,10 @@ const deep_proto_merge = (replacement, delegate, options) => {
// Combine and deduplicate properties using a Set, then convert back to an array
const s = new Set([
...protoProps,
...ownProps
...ownProps,
]);
if (options?.preserve_flag) {
if ( options?.preserve_flag ) {
// remove $preserve if it exists
s.delete('$preserve');
}
@@ -76,16 +74,16 @@ const deep_proto_merge = (replacement, delegate, options) => {
// Real descriptor
let descriptor = Object.getOwnPropertyDescriptor(target, prop);
if (descriptor) return descriptor;
if ( descriptor ) return descriptor;
// Immediate prototype descriptor
const proto = Object.getPrototypeOf(target);
descriptor = Object.getOwnPropertyDescriptor(proto, prop);
if (descriptor) return descriptor;
if ( descriptor ) return descriptor;
return undefined;
}
},
});

View File

@@ -1,18 +1,18 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
*
* This file is part of Puter.
*
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -92,7 +92,7 @@ const policy_perm = selector => ({
$: 'json-address',
path: '/admin/.policy/drivers.json',
selector,
}
},
});
const hardcoded_user_group_permissions = {

View File

@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const BaseService = require("../services/BaseService");
const BaseService = require('../services/BaseService');
class Library extends BaseService {
//

View File

@@ -16,21 +16,21 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { Context } = require("../util/context");
const { Context } = require('../util/context');
module.exports = function SimpleEntity ({ name, methods, fetchers }) {
const create = function (values) {
const entity = { values };
Object.assign(entity, methods);
for ( const fetcher_name in fetchers ) {
entity['fetch_' + fetcher_name] = async function () {
entity[`fetch_${ fetcher_name}`] = async function () {
if ( this.values.hasOwnProperty(fetcher_name) ) {
return this.values[fetcher_name];
}
const value = await fetchers[fetcher_name].call(this);
this.values[fetcher_name] = value;
return value;
}
};
}
entity.context = values.context ?? Context.get();
entity.services = entity.context.get('services');

View File

@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const SimpleEntity = require("../definitions/SimpleEntity");
const SimpleEntity = require('../definitions/SimpleEntity');
module.exports = SimpleEntity({
name: 'group',
@@ -25,7 +25,7 @@ module.exports = SimpleEntity({
const svc_group = this.services.get('group');
const members = await svc_group.list_members({ uid: this.values.uid });
return members;
}
},
},
methods: {
async get_client_value (options = {}) {
@@ -38,6 +38,6 @@ module.exports = SimpleEntity({
...(options.members ? { members: this.values.members } : {}),
};
return group;
}
}
},
},
});

View File

@@ -37,7 +37,7 @@ class TechnicalError extends Error {
const ERR_HINT_NOSTACK = e => {
e.toString = () => e.message;
}
};
module.exports = {
TechnicalError,

View File

@@ -34,12 +34,12 @@ const reused = {
subject: 'RuntimeEnvironment.js',
location: 'src/boot/ in repository',
use: 'code that performs the checks',
}
]
},
],
};
const programmer_errors = [
'Assignment to constant variable.'
'Assignment to constant variable.',
];
const error_help_details = [
@@ -51,7 +51,7 @@ const error_help_details = [
more.references = [
...reused.runtime_env_references,
];
}
},
},
{
match: ({ message }) => (
@@ -72,7 +72,7 @@ const error_help_details = [
more.references = [
...reused.runtime_env_references,
];
}
},
},
{
match: ({ message }) => (
@@ -84,21 +84,19 @@ const error_help_details = [
title: 'Create a valid config file',
},
];
}
},
},
{
match: ({ message }) => (
message === `config_name is required`
message === 'config_name is required'
),
apply (more) {
more.solutions = [
'ensure config_name is present in your config file',
'Seek help on ' + osclink(
'https://discord.gg/PQcx7Teh8u',
'our Discord server'
),
`Seek help on ${ osclink('https://discord.gg/PQcx7Teh8u',
'our Discord server')}`,
];
}
},
},
{
match: ({ message }) => (
@@ -110,10 +108,10 @@ const error_help_details = [
subject: 'MDN Reference for this error',
location: 'on the internet',
use: 'describes why this error occurs',
url: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_const_assignment'
url: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_const_assignment',
},
];
}
},
},
{
match: ({ message }) => (
@@ -125,18 +123,16 @@ const error_help_details = [
];
more.solutions = [
{
title: `Check for an issue on ` +
osclink('https://github.com/HeyPuter/puter/issues')
title: `Check for an issue on ${
osclink('https://github.com/HeyPuter/puter/issues')}`,
},
{
title: `If there's no issue, please ` +
osclink(
'https://github.com/HeyPuter/puter/issues/new',
'create one'
) + '.'
}
title: `If there's no issue, please ${
osclink('https://github.com/HeyPuter/puter/issues/new',
'create one') }.`,
},
];
}
},
},
{
match: ({ message }) => (
@@ -146,8 +142,8 @@ const error_help_details = [
more.notes = [
'There might be a trailing-comma in your config',
];
}
}
},
},
];
/**
@@ -178,13 +174,13 @@ const print_error_help = (err, out = process.stdout) => {
const wrap_list_title = s =>
`\x1B[36;1m${s}:\x1B[0m`;
write(wrap_msg(err.message) + '\n');
write(`${wrap_msg(err.message) }\n`);
write = (s) => out.write('\x1B[31;1m┃\x1B[0m ' + s);
write = (s) => out.write(`\x1B[31;1m┃\x1B[0m ${ s}`);
const vis = (stok, etok, str) => {
return `\x1B[36;1m${stok}\x1B[0m${str}\x1B[36;1m${etok}\x1B[0m`;
}
};
let lf_sep = false;
@@ -204,9 +200,9 @@ const print_error_help = (err, out = process.stdout) => {
if ( lf_sep ) write('\n');
lf_sep = true;
any_help = true;
write('The suggestions below may help resolve this issue.\n')
write('The suggestions below may help resolve this issue.\n');
write('\n');
write(wrap_list_title('Possible Solutions') + '\n');
write(`${wrap_list_title('Possible Solutions') }\n`);
for ( const sol of err.more.solutions ) {
write(` - ${sol.title}\n`);
}
@@ -216,9 +212,9 @@ const print_error_help = (err, out = process.stdout) => {
if ( lf_sep ) write('\n');
lf_sep = true;
any_help = true;
write('The references below may be related to this issue.\n')
write('The references below may be related to this issue.\n');
write('\n');
write(wrap_list_title('References') + '\n');
write(`${wrap_list_title('References') }\n`);
for ( const ref of err.more.references ) {
write(` - ${vis('[', ']', ref.subject)} ` +
`${vis('(', ')', ref.location)};\n`);
@@ -234,9 +230,9 @@ const print_error_help = (err, out = process.stdout) => {
write('Help can be added in src/errors/error_help_details.\n');
}
out.write(`\x1B[31;1m┗━━ [ END HELP ]\x1B[0m\n`)
out.write('\x1B[31;1m┗━━ [ END HELP ]\x1B[0m\n');
out.write('\n');
}
};
module.exports = {
error_help_details,

View File

@@ -1,4 +1,4 @@
const { AdvancedBase } = require("@heyputer/putility");
const { AdvancedBase } = require('@heyputer/putility');
class RuntimeModule extends AdvancedBase {
constructor (options = {}) {
@@ -6,7 +6,7 @@ class RuntimeModule extends AdvancedBase {
this.exports_ = undefined;
this.exports_is_set_ = false;
this.remappings = options.remappings ?? {};
this.name = options.name ?? undefined;
}
set exports (value) {

View File

@@ -1,12 +1,12 @@
const { AdvancedBase } = require("@heyputer/putility");
const { RuntimeModule } = require("./RuntimeModule");
const { AdvancedBase } = require('@heyputer/putility');
const { RuntimeModule } = require('./RuntimeModule');
class RuntimeModuleRegistry extends AdvancedBase {
constructor () {
super();
this.modules_ = {};
}
register (extensionModule, options = {}) {
if ( ! (extensionModule instanceof RuntimeModule) ) {
throw new Error(`expected a RuntimeModule, but got: ${
@@ -19,7 +19,7 @@ class RuntimeModuleRegistry extends AdvancedBase {
this.modules_[uniqueName] = extensionModule;
extensionModule.runtimeModuleRegistry = this;
}
exportsOf (name) {
if ( ! this.modules_[name] ) {
throw new Error(`could not find runtime module: ${name}`);
@@ -29,5 +29,5 @@ class RuntimeModuleRegistry extends AdvancedBase {
}
module.exports = {
RuntimeModuleRegistry
RuntimeModuleRegistry,
};

View File

@@ -1,5 +1,5 @@
const { Context } = require("../util/context");
const { NodeUIDSelector, NodePathSelector, NodeInternalIDSelector } = require("./node/selectors");
const { Context } = require('../util/context');
const { NodeUIDSelector, NodePathSelector, NodeInternalIDSelector } = require('./node/selectors');
const LOG_PREFIX = '\x1B[31;1m[[\x1B[33;1mEC\x1B[32;1mMAP\x1B[31;1m]]\x1B[0m';
@@ -8,28 +8,28 @@ const LOG_PREFIX = '\x1B[31;1m[[\x1B[33;1mEC\x1B[32;1mMAP\x1B[31;1m]]\x1B[0m';
* whenever it is present in the execution context (AsyncLocalStorage).
* It is assumed that this object is transient and invalidation of stale
* entries is not necessary.
*
*
* The name ECMAP simple means Execution Context Map, because the map
* exists in memory at a particular frame of the execution context.
*/
class ECMAP {
static SYMBOL = Symbol('ECMAP');
constructor () {
this.identifier = require('uuid').v4();
// entry caches
this.uuid_to_fsNodeContext = {};
this.path_to_fsNodeContext = {};
this.id_to_fsNodeContext = {};
// identifier association caches
this.path_to_uuid = {};
this.uuid_to_path = {};
this.unlinked = false;
}
/**
* unlink() clears all references from this ECMAP to ensure that it will be
* GC'd. This is called by ECMAP.arun() after the callback has resolved.
@@ -42,16 +42,16 @@ class ECMAP {
this.path_to_uuid = null;
this.uuid_to_path = null;
}
get logPrefix () {
return `${LOG_PREFIX} \x1B[36[1m${this.identifier}\x1B[0m`;
}
log (...a) {
if ( ! process.env.LOG_ECMAP ) return;
console.log(this.logPrefix, ...a);
}
get_fsNodeContext_from_selector (selector) {
if ( this.unlinked ) return null;
@@ -61,21 +61,21 @@ class ECMAP {
if ( selector instanceof NodeUIDSelector ) {
value = this.uuid_to_fsNodeContext[selector.value];
if ( value ) return value;
let maybe_path = this.uuid_to_path[value];
if ( ! maybe_path ) return;
value = this.path_to_fsNodeContext[maybe_path];
if ( value ) return value;
}
else
if ( selector instanceof NodePathSelector ) {
value = this.path_to_fsNodeContext[selector.value];
if ( value ) return value;
let maybe_uid = this.path_to_uuid[value];
value = this.uuid_to_fsNodeContext[maybe_uid];
if ( value ) return value;
}
if ( selector instanceof NodePathSelector ) {
value = this.path_to_fsNodeContext[selector.value];
if ( value ) return value;
let maybe_uid = this.path_to_uuid[value];
value = this.uuid_to_fsNodeContext[maybe_uid];
if ( value ) return value;
}
})();
if ( retvalue ) {
this.log('\x1B[32;1m <<<<< ECMAP HIT >>>>> \x1B[0m');
@@ -84,7 +84,7 @@ class ECMAP {
}
return retvalue;
}
store_fsNodeContext_to_selector (selector, node) {
if ( this.unlinked ) return null;
@@ -96,16 +96,16 @@ class ECMAP {
this.path_to_fsNodeContext[selector.value] = node;
}
if ( selector instanceof NodeInternalIDSelector ) {
this.id_to_fsNodeContext[selector.service+':'+selector.id] = node;
this.id_to_fsNodeContext[`${selector.service}:${selector.id}`] = node;
}
}
store_fsNodeContext (node) {
if ( this.unlinked ) return;
this.store_fsNodeContext_to_selector(node.selector, node);
}
static async arun (cb) {
let context = Context.get();
if ( ! context.get(this.SYMBOL) ) {

View File

@@ -16,19 +16,19 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { get_user, id2path, id2uuid, is_empty, suggest_app_for_fsentry, get_app } = require("../helpers");
const { get_user, id2path, id2uuid, is_empty, suggest_app_for_fsentry, get_app } = require('../helpers');
const putility = require('@heyputer/putility');
const config = require("../config");
const config = require('../config');
const _path = require('path');
const { NodeInternalIDSelector, NodeChildSelector, NodeUIDSelector, RootNodeSelector, NodePathSelector } = require("./node/selectors");
const { Context } = require("../util/context");
const { NodeRawEntrySelector } = require("./node/selectors");
const { DB_READ } = require("../services/database/consts");
const { UserActorType, AppUnderUserActorType, Actor } = require("../services/auth/Actor");
const { PermissionUtil } = require("../services/auth/permissionUtils.mjs");
const { ECMAP } = require("./ECMAP");
const { MANAGE_PERM_PREFIX } = require("../services/auth/permissionConts.mjs");
const { NodeInternalIDSelector, NodeChildSelector, NodeUIDSelector, RootNodeSelector, NodePathSelector } = require('./node/selectors');
const { Context } = require('../util/context');
const { NodeRawEntrySelector } = require('./node/selectors');
const { DB_READ } = require('../services/database/consts');
const { UserActorType, AppUnderUserActorType, Actor } = require('../services/auth/Actor');
const { PermissionUtil } = require('../services/auth/permissionUtils.mjs');
const { ECMAP } = require('./ECMAP');
const { MANAGE_PERM_PREFIX } = require('../services/auth/permissionConts.mjs');
/**
* Container for information collected about a node
@@ -73,7 +73,7 @@ module.exports = class FSNodeContext {
* @param {*} opt_identifier.id please pass mysql_id instead
* @param {*} opt_identifier.mysql_id a MySQL ID of the filesystem entry
*/
constructor({
constructor ({
services,
selector,
provider,
@@ -147,7 +147,7 @@ module.exports = class FSNodeContext {
}
}
set selector(new_selector) {
set selector (new_selector) {
// Only add the selector if we don't already have it
for ( const selector of this.selectors_ ) {
if ( selector instanceof new_selector.constructor ) return;
@@ -162,11 +162,11 @@ module.exports = class FSNodeContext {
this.selector_ = new_selector;
}
get selector() {
get selector () {
return this.get_optimal_selector();
}
get_selector_of_type(cls) {
get_selector_of_type (cls) {
// Reverse iterate over selectors
for ( let i = this.selectors_.length - 1; i >= 0; i-- ) {
const selector = this.selectors_[i];
@@ -182,7 +182,7 @@ module.exports = class FSNodeContext {
return null;
}
get_optimal_selector() {
get_optimal_selector () {
for ( const cls of FSNodeContext.SELECTOR_PRIORITY_ORDER ) {
const selector = this.get_selector_of_type(cls);
if ( selector ) return selector;
@@ -191,21 +191,21 @@ module.exports = class FSNodeContext {
return this.selector_;
}
get isRoot() {
get isRoot () {
return this.path === '/';
}
async isUserDirectory() {
async isUserDirectory () {
if ( this.isRoot ) return false;
if ( this.found === undefined ) {
await this.fetchEntry();
}
if ( this.isRoot ) return false;
if ( this.found === false ) return undefined;
return ! this.entry.parent_uid;
return !this.entry.parent_uid;
}
async isAppDataDirectory() {
async isAppDataDirectory () {
if ( this.isRoot ) return false;
if ( this.found === undefined ) {
await this.fetchEntry();
@@ -217,7 +217,7 @@ module.exports = class FSNodeContext {
return components[1] === 'AppData';
}
async isPublic() {
async isPublic () {
if ( this.isRoot ) return false;
const components = await this.getPathComponents();
if ( await this.isUserDirectory() ) return false;
@@ -225,7 +225,7 @@ module.exports = class FSNodeContext {
return false;
}
async getPathComponents() {
async getPathComponents () {
if ( this.isRoot ) return [];
// We can get path components for non-existing nodes if they
@@ -245,30 +245,30 @@ module.exports = class FSNodeContext {
return path.split('/');
}
async getUserPart() {
async getUserPart () {
if ( this.isRoot ) return;
const components = await this.getPathComponents();
return components[0];
}
async getPathSize() {
async getPathSize () {
if ( this.isRoot ) return;
const components = await this.getPathComponents();
return components.length;
}
async exists({ fetch_options } = {}) {
async exists ({ fetch_options } = {}) {
await this.fetchEntry(fetch_options);
if ( ! this.found ) {
this.log.debug('here\'s why it doesn\'t exist: ' +
this.selector.describe() + ' -> ' +
this.uid + ' ' +
JSON.stringify(this.entry, null, ' '));
this.log.debug(`here's why it doesn't exist: ${
this.selector.describe() } -> ${
this.uid } ${
JSON.stringify(this.entry, null, ' ')}`);
}
return this.found;
}
async fetchPath() {
async fetchPath () {
if ( this.path ) return;
this.path = await this.services.get('information')
@@ -299,10 +299,10 @@ module.exports = class FSNodeContext {
if (
this.found === true &&
! fetch_entry_options.force &&
!fetch_entry_options.force &&
(
// thumbnail already fetched, or not asked for
! fetch_entry_options.thumbnail || this.entry?.thumbnail ||
!fetch_entry_options.thumbnail || this.entry?.thumbnail ||
this.found_thumbnail !== undefined
)
) {
@@ -319,7 +319,7 @@ module.exports = class FSNodeContext {
},
};
this.log.debug('fetching entry: ' + this.selector.describe());
this.log.debug(`fetching entry: ${ this.selector.describe()}`);
const entry = await this.provider.stat({
selector: this.selector,
@@ -334,19 +334,19 @@ module.exports = class FSNodeContext {
} else {
this.found = true;
if ( ! this.uid && entry.uuid ) {
if ( !this.uid && entry.uuid ) {
this.uid = entry.uuid;
}
if ( ! this.mysql_id && entry.id ) {
if ( !this.mysql_id && entry.id ) {
this.mysql_id = entry.id;
}
if ( ! this.path && entry.path ) {
if ( !this.path && entry.path ) {
this.path = entry.path;
}
if ( ! this.name && entry.name ) {
if ( !this.name && entry.name ) {
this.name = entry.name;
}
@@ -365,7 +365,7 @@ module.exports = class FSNodeContext {
*
* This just calls ResourceService under the hood.
*/
async awaitStableEntry() {
async awaitStableEntry () {
const resourceService = Context.get('services').get('resourceService');
await resourceService.waitForResource(this.selector);
}
@@ -378,19 +378,19 @@ module.exports = class FSNodeContext {
*
* @param fs:decouple-subdomains
*/
async fetchSubdomains(user, _force) {
async fetchSubdomains (user, _force) {
if ( ! this.entry.is_dir ) return;
const db = this.services.get('database').get(DB_READ, 'filesystem');
this.entry.subdomains = [];
let subdomains = await db.read(`SELECT * FROM subdomains WHERE root_dir_id = ? AND user_id = ?`,
let subdomains = await db.read('SELECT * FROM subdomains WHERE root_dir_id = ? AND user_id = ?',
[this.entry.id, user.id]);
if ( subdomains.length > 0 ){
if ( subdomains.length > 0 ) {
subdomains.forEach((sd) => {
this.entry.subdomains.push({
subdomain: sd.subdomain,
address: config.protocol + '://' + sd.subdomain + "." + 'puter.site',
address: `${config.protocol }://${ sd.subdomain }.` + 'puter.site',
uuid: sd.uuid,
});
});
@@ -403,7 +403,7 @@ module.exports = class FSNodeContext {
* `owner` property of the fsentry.
* @param {bool} force fetch owner if it was already fetched
*/
async fetchOwner(_force) {
async fetchOwner (_force) {
if ( this.isRoot ) return;
const owner = await get_user({ id: this.entry.user_id });
this.entry.owner = {
@@ -418,8 +418,8 @@ module.exports = class FSNodeContext {
* of the fsentry.
* @param {bool} force fetch shares if they were already fetched
*/
async fetchShares(force) {
if ( this.entry.shares && ! force ) return;
async fetchShares (force) {
if ( this.entry.shares && !force ) return;
const actor = Context.get('actor');
if ( ! actor ) {
@@ -503,12 +503,12 @@ module.exports = class FSNodeContext {
*
* @todo fs:decouple-versions
*/
async fetchVersions(force) {
if ( this.entry.versions && ! force ) return;
async fetchVersions (force) {
if ( this.entry.versions && !force ) return;
const db = this.services.get('database').get(DB_READ, 'filesystem');
let versions = await db.read(`SELECT * FROM fsentry_versions WHERE fsentry_id = ?`,
let versions = await db.read('SELECT * FROM fsentry_versions WHERE fsentry_id = ?',
[this.entry.id]);
const versions_tidy = [];
for ( const version of versions ) {
@@ -530,7 +530,7 @@ module.exports = class FSNodeContext {
* Fetches the size of a file or directory if it was not
* already fetched.
*/
async fetchSize() {
async fetchSize () {
// we already have the size for files
if ( ! this.entry.is_dir ) {
await this.fetchEntry();
@@ -542,8 +542,8 @@ module.exports = class FSNodeContext {
return this.entry.size;
}
async fetchSuggestedApps(user, force) {
if ( this.entry.suggested_apps && ! force ) return;
async fetchSuggestedApps (user, force) {
if ( this.entry.suggested_apps && !force ) return;
await this.fetchEntry();
if ( ! this.entry ) return;
@@ -552,15 +552,15 @@ module.exports = class FSNodeContext {
await suggest_app_for_fsentry(this.entry, { user });
}
async fetchIsEmpty() {
if ( ! this.uid && ! this.path ) return;
async fetchIsEmpty () {
if ( !this.uid && !this.path ) return;
this.entry.is_empty = await is_empty({
uid: this.uid,
path: this.path,
});
}
async fetchAll(_fsEntryFetcher, user, _force) {
async fetchAll (_fsEntryFetcher, user, _force) {
await this.fetchEntry({ thumbnail: true });
await this.fetchSubdomains(user);
await this.fetchOwner();
@@ -571,7 +571,7 @@ module.exports = class FSNodeContext {
await this.fetchIsEmpty();
}
async get(key) {
async get (key) {
/*
This isn't supposed to stay like this!
@@ -585,15 +585,15 @@ module.exports = class FSNodeContext {
*/
if ( this.found === false ) {
throw new Error(`Tried to get ${key} of non-existent fsentry: ` +
this.selector.describe(true));
throw new Error(`Tried to get ${key} of non-existent fsentry: ${
this.selector.describe(true)}`);
}
if ( key === 'entry' ) {
await this.fetchEntry();
if ( this.found === false ) {
throw new Error(`Tried to get entry of non-existent fsentry: ` +
this.selector.describe(true));
throw new Error(`Tried to get entry of non-existent fsentry: ${
this.selector.describe(true)}`);
}
return this.entry;
}
@@ -601,14 +601,14 @@ module.exports = class FSNodeContext {
if ( key === 'path' ) {
if ( ! this.path ) await this.fetchEntry();
if ( this.found === false ) {
throw new Error(`Tried to get path of non-existent fsentry: ` +
this.selector.describe(true));
throw new Error(`Tried to get path of non-existent fsentry: ${
this.selector.describe(true)}`);
}
if ( ! this.path ) {
await this.fetchPath();
}
if ( ! this.path ) {
throw new Error(`failed to get path`);
throw new Error('failed to get path');
}
return this.path;
}
@@ -642,8 +642,8 @@ module.exports = class FSNodeContext {
if ( key === k ) {
await this.fetchEntry();
if ( this.found === false ) {
throw new Error(`Tried to get ${key} of non-existent fsentry: ` +
this.selector.describe(true));
throw new Error(`Tried to get ${key} of non-existent fsentry: ${
this.selector.describe(true)}`);
}
return this.entry[k];
}
@@ -700,7 +700,7 @@ module.exports = class FSNodeContext {
throw new Error(`unrecognize key for FSNodeContext.get: ${key}`);
}
async getParent() {
async getParent () {
if ( this.isRoot ) {
throw new Error('tried to get parent of root');
}
@@ -729,7 +729,7 @@ module.exports = class FSNodeContext {
return this.fs.node(new NodeUIDSelector(parent_uid));
}
async getChild(name) {
async getChild (name) {
// If we have a path, we can get an FSNodeContext for the child
// without fetching anything.
if ( this.path ) {
@@ -741,12 +741,12 @@ module.exports = class FSNodeContext {
return await this.fs.node(new NodeChildSelector(this.selector, name));
}
async hasChild(name) {
async hasChild (name) {
return await this.provider.directory_has_name({ parent: this, name });
}
async getTarget() {
async getTarget () {
await this.fetchEntry();
const type = await this.get('type');
@@ -763,16 +763,16 @@ module.exports = class FSNodeContext {
return this;
}
async is_above(child_fsNode) {
async is_above (child_fsNode) {
if ( this.isRoot ) return true;
const path_this = await this.get('path');
const path_child = await child_fsNode.get('path');
return path_child.startsWith(path_this + '/');
return path_child.startsWith(`${path_this }/`);
}
async is(fsNode) {
async is (fsNode) {
if ( this.mysql_id && fsNode.mysql_id ) {
return this.mysql_id === fsNode.mysql_id;
}
@@ -786,19 +786,19 @@ module.exports = class FSNodeContext {
return this.uid === fsNode.uid;
}
async getSafeEntry(fetch_options = {}) {
async getSafeEntry (fetch_options = {}) {
const svc_event = this.services.get('event');
if ( this.found === false ) {
throw new Error(`Tried to get entry of non-existent fsentry: ` +
this.selector.describe(true));
throw new Error(`Tried to get entry of non-existent fsentry: ${
this.selector.describe(true)}`);
}
await this.fetchEntry(fetch_options);
const res = this.entry;
const fsentry = {};
if (res.thumbnail) {
await svc_event.emit("thumbnail.read", this.entry);
if ( res.thumbnail ) {
await svc_event.emit('thumbnail.read', this.entry);
}
// This property will not be serialized, but it can be checked
@@ -818,7 +818,7 @@ module.exports = class FSNodeContext {
} catch ( _e ) {
// fail silently
}
if ( ! actor?.type?.user || actor.type.user.id !== res.user_id ) {
if ( !actor?.type?.user || actor.type.user.id !== res.user_id ) {
if ( ! fsentry.owner ) await this.fetchOwner();
fsentry.owner = {
username: res.owner?.username,
@@ -830,10 +830,10 @@ module.exports = class FSNodeContext {
const info = this.services.get('information');
if ( ! this.uid && ! this.entry.uuid ) {
this.log.noticeme('whats even happening!?!? ' +
this.selector.describe() + ' ' +
JSON.stringify(this.entry, null, ' '));
if ( !this.uid && !this.entry.uuid ) {
this.log.noticeme(`whats even happening!?!? ${
this.selector.describe() } ${
JSON.stringify(this.entry, null, ' ')}`);
}
// If fsentry was found by a path but the entry doesn't
@@ -884,9 +884,9 @@ module.exports = class FSNodeContext {
}
// Add file_request_url
if ( res.file_request_token && res.file_request_token !== '' ){
fsentry.file_request_url = config.origin +
'/upload?token=' + res.file_request_token;
if ( res.file_request_token && res.file_request_token !== '' ) {
fsentry.file_request_url = `${config.origin
}/upload?token=${ res.file_request_token}`;
}
if ( fsentry.associated_app_id ) {
@@ -900,7 +900,7 @@ module.exports = class FSNodeContext {
fsentry.appdata_app = components[2];
}
fsentry.is_dir = !! fsentry.is_dir;
fsentry.is_dir = !!fsentry.is_dir;
// Ensure `size` is numeric
if ( fsentry.size ) {
@@ -910,7 +910,7 @@ module.exports = class FSNodeContext {
return fsentry;
}
static sanitize_pending_entry_info(res) {
static sanitize_pending_entry_info (res) {
const fsentry = {};
// This property will not be serialized, but it can be checked

View File

@@ -31,7 +31,7 @@ class BatchExecutor extends AdvancedBase {
constructor (x, { actor, log, errors }) {
super();
this.x = x;
this.actor = actor
this.actor = actor;
this.pathResolver = new PathResolver({ actor });
this.expectations = x.get('services').get('expectations');
this.log = log;
@@ -46,7 +46,7 @@ class BatchExecutor extends AdvancedBase {
this.concurrent_ops = 0;
this.max_concurrent_ops = 20;
this.ops_promise = null;
this.log_batchCommands = (config.logging ?? []).includes('batch-commands');
}
@@ -78,7 +78,7 @@ class BatchExecutor extends AdvancedBase {
const workUnit = WorkUnit.create();
expectations.expect_eventually({
workUnit,
checkpoint: 'operation responded'
checkpoint: 'operation responded',
});
// TEMP: event service will handle this
@@ -87,7 +87,7 @@ class BatchExecutor extends AdvancedBase {
// run the operation
let p = this.x.arun(async () => {
const x= Context.get();
const x = Context.get();
if ( ! x ) throw new Error('no context');
try {
@@ -97,12 +97,12 @@ class BatchExecutor extends AdvancedBase {
});
}
if ( file ) workUnit.checkpoint(
'about to run << ' +
(file.originalname ?? file.name) +
' >> ' +
JSON.stringify(op)
);
if ( file ) {
workUnit.checkpoint(`about to run << ${
file.originalname ?? file.name
} >> ${
JSON.stringify(op)}`);
}
const command_ins = await command_cls.run({
getFile: () => file,
pathResolver: this.pathResolver,

View File

@@ -16,27 +16,26 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { AdvancedBase } = require("@heyputer/putility");
const { AsyncProviderFeature } = require("../../traits/AsyncProviderFeature");
const { HLMkdir, QuickMkdir } = require("../hl_operations/hl_mkdir");
const { Context } = require("../../util/context");
const { HLWrite } = require("../hl_operations/hl_write");
const { get_app } = require("../../helpers");
const { OperationFrame } = require("../../services/OperationTraceService");
const { HLMkShortcut } = require("../hl_operations/hl_mkshortcut");
const { HLMkLink } = require("../hl_operations/hl_mklink");
const { HLRemove } = require("../hl_operations/hl_remove");
const { AdvancedBase } = require('@heyputer/putility');
const { AsyncProviderFeature } = require('../../traits/AsyncProviderFeature');
const { HLMkdir, QuickMkdir } = require('../hl_operations/hl_mkdir');
const { Context } = require('../../util/context');
const { HLWrite } = require('../hl_operations/hl_write');
const { get_app } = require('../../helpers');
const { OperationFrame } = require('../../services/OperationTraceService');
const { HLMkShortcut } = require('../hl_operations/hl_mkshortcut');
const { HLMkLink } = require('../hl_operations/hl_mklink');
const { HLRemove } = require('../hl_operations/hl_remove');
class BatchCommand extends AdvancedBase {
static FEATURES = [
new AsyncProviderFeature(),
]
];
static async run (executor, parameters) {
const instance = new this();
let x = Context.get();
const operationTraceSvc = x.get('services').get('operationTrace');
const frame = await operationTraceSvc.add_frame('batch:' + this.name);
const frame = await operationTraceSvc.add_frame(`batch:${ this.name}`);
if ( parameters.hasOwnProperty('item_upload_id') ) {
frame.attr('gui_metadata', {
...(frame.get_attr('gui_metadata') || {}),
@@ -73,11 +72,9 @@ class MkdirCommand extends BatchCommand {
path: parameters.path,
});
if ( parameters.as ) {
executor.pathResolver.putSelector(
parameters.as,
q_mkdir.created.selector,
{ conflict_free: true }
);
executor.pathResolver.putSelector(parameters.as,
q_mkdir.created.selector,
{ conflict_free: true });
}
this.setFactory('result', async () => {
await q_mkdir.created.awaitStableEntry();
@@ -101,15 +98,13 @@ class MkdirCommand extends BatchCommand {
actor: executor.actor,
});
if ( parameters.as ) {
executor.pathResolver.putSelector(
parameters.as,
hl_mkdir.created.selector,
hl_mkdir.used_existing
? undefined
: { conflict_free: true }
);
executor.pathResolver.putSelector(parameters.as,
hl_mkdir.created.selector,
hl_mkdir.used_existing
? undefined
: { conflict_free: true });
}
this.provideValue('result', response)
this.provideValue('result', response);
}
}
@@ -125,7 +120,7 @@ class WriteCommand extends BatchCommand {
let app;
if ( parameters.app_uid ) {
app = await get_app({uid: parameters.app_uid})
app = await get_app({ uid: parameters.app_uid });
}
const hl_write = new HLWrite();
@@ -158,7 +153,6 @@ class WriteCommand extends BatchCommand {
this.provideValue('result', response);
// const opctx = await fs.write(fs, {
// // --- per file ---
// name: parameters.name,
@@ -197,7 +191,7 @@ class ShortcutCommand extends BatchCommand {
let app;
if ( parameters.app_uid ) {
app = await get_app({uid: parameters.app_uid})
app = await get_app({ uid: parameters.app_uid });
}
await destinationOrParent.fetchEntry({ thumbnail: true });
@@ -232,7 +226,7 @@ class SymlinkCommand extends BatchCommand {
let app;
if ( parameters.app_uid ) {
app = await get_app({uid: parameters.app_uid})
app = await get_app({ uid: parameters.app_uid });
}
await destinationOrParent.fetchEntry({ thumbnail: true });
@@ -281,5 +275,5 @@ module.exports = {
shortcut: ShortcutCommand,
symlink: SymlinkCommand,
delete: DeleteCommand,
}
},
};

View File

@@ -1,10 +1,10 @@
"use strict";
'use strict';
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions:
// protoc-gen-ts_proto v2.8.0
// protoc v3.21.12
// source: fsentry.proto
Object.defineProperty(exports, "__esModule", { value: true });
Object.defineProperty(exports, '__esModule', { value: true });
exports.FSEntry = exports.protobufPackage = void 0;
/* eslint-disable */
const wire_1 = require("@bufbuild/protobuf/wire");

View File

@@ -18,8 +18,9 @@
*/
const { BaseOperation } = require('../../services/OperationTraceService');
class HLFilesystemOperation extends BaseOperation {}
class HLFilesystemOperation extends BaseOperation {
}
module.exports = {
HLFilesystemOperation
HLFilesystemOperation,
};

View File

@@ -16,14 +16,14 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("../../api/APIError");
const { chkperm, validate_fsentry_name, get_user, is_ancestor_of } = require("../../helpers");
const { TYPE_DIRECTORY } = require("../FSNodeContext");
const { NodePathSelector, RootNodeSelector } = require("../node/selectors");
const { HLFilesystemOperation } = require("./definitions");
const { MkTree } = require("./hl_mkdir");
const { HLRemove } = require("./hl_remove");
const { LLCopy } = require("../ll_operations/ll_copy");
const APIError = require('../../api/APIError');
const { chkperm, validate_fsentry_name, get_user, is_ancestor_of } = require('../../helpers');
const { TYPE_DIRECTORY } = require('../FSNodeContext');
const { NodePathSelector, RootNodeSelector } = require('../node/selectors');
const { HLFilesystemOperation } = require('./definitions');
const { MkTree } = require('./hl_mkdir');
const { HLRemove } = require('./hl_remove');
const { LLCopy } = require('../ll_operations/ll_copy');
class HLCopy extends HLFilesystemOperation {
static DESCRIPTION = `
@@ -34,11 +34,11 @@ class HLCopy extends HLFilesystemOperation {
- create missing parent directories
- overwrite existing files or directories
- deduplicate files/directories with the same name
`
`;
static MODULES = {
_path: require('path'),
}
};
static PARAMETERS = {
source: {},
@@ -51,7 +51,7 @@ class HLCopy extends HLFilesystemOperation {
create_missing_parents: {},
user: {},
}
};
async _run () {
const { _path } = this.modules;
@@ -84,7 +84,7 @@ class HLCopy extends HLFilesystemOperation {
// If parent exists and is a file, and a new name wasn't
// specified, the intention must be to overwrite the file.
if (
! values.new_name &&
!values.new_name &&
await parent.exists() &&
await parent.get('type') !== TYPE_DIRECTORY
) {
@@ -130,20 +130,20 @@ class HLCopy extends HLFilesystemOperation {
// NEXT: implement _verify_room with profiling
const tracer = svc.get('traceService').tracer;
await tracer.startActiveSpan(`fs:cp:verify-size-constraints`, async span => {
await tracer.startActiveSpan('fs:cp:verify-size-constraints', async span => {
const source_file = source.entry;
const dest_fsentry = parent.entry;
let source_user = await get_user({id: source_file.user_id});
let source_user = await get_user({ id: source_file.user_id });
let dest_user = source_user.id !== dest_fsentry.user_id
? await get_user({id: dest_fsentry.user_id})
? await get_user({ id: dest_fsentry.user_id })
: source_user ;
const sizeService = svc.get('sizeService');
let deset_usage = await sizeService.get_usage(dest_user.id);
const size = await source.fetchSize();
const capacity = await sizeService.get_storage_capacity(dest_user.id);
if(capacity - deset_usage - size < 0){
if ( capacity - deset_usage - size < 0 ) {
throw APIError.create('storage_limit_reached');
}
span.end();
@@ -166,16 +166,16 @@ class HLCopy extends HLFilesystemOperation {
let overwritten;
if ( await dest.exists() ) {
// condition: no overwrite behaviour specified
if ( ! values.overwrite && ! values.dedupe_name ) {
if ( !values.overwrite && !values.dedupe_name ) {
throw APIError.create('item_with_same_name_exists', null, {
entry_name: dest.entry.name
entry_name: dest.entry.name,
});
}
if ( values.dedupe_name ) {
const target_ext = _path.extname(target_name);
const target_noext = _path.basename(target_name, target_ext);
for ( let i=1 ;; i++ ) {
for ( let i = 1 ;; i++ ) {
const try_new_name = `${target_noext} (${i})${target_ext}`;
const exists = await parent.hasChild(try_new_name);
if ( ! exists ) {
@@ -209,13 +209,13 @@ class HLCopy extends HLFilesystemOperation {
parent,
user: values.user,
target_name,
})
});
await this.copied.awaitStableEntry();
const response = await this.copied.getSafeEntry({ thumbnail: true });
return {
copied : response,
overwritten
copied: response,
overwritten,
};
}
}

View File

@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { HLFilesystemOperation } = require("./definitions");
const { HLFilesystemOperation } = require('./definitions');
const { chkperm } = require('../../helpers');
const { LLRead } = require('../ll_operations/ll_read');
const APIError = require('../../api/APIError');
@@ -29,7 +29,7 @@ const APIError = require('../../api/APIError');
class HLDataRead extends HLFilesystemOperation {
static MODULES = {
'stream': require('stream'),
}
};
async _run () {
const { context } = this;
@@ -53,7 +53,8 @@ class HLDataRead extends HLFilesystemOperation {
const ll_read = new LLRead();
let stream = await ll_read.run({
fsNode, user,
fsNode,
user,
version_id,
});
@@ -66,8 +67,8 @@ class HLDataRead extends HLFilesystemOperation {
_stream_bytes_to_lines (stream) {
const readline = require('readline');
const rl = readline.createInterface({
input: stream,
terminal: false
input: stream,
terminal: false,
});
const { PassThrough } = this.modules.stream;
@@ -88,7 +89,7 @@ class HLDataRead extends HLFilesystemOperation {
const { PassThrough } = this.modules.stream;
const output_stream = new PassThrough();
(async () => {
for await (const line of stream) {
for await ( const line of stream ) {
output_stream.write(JSON.parse(line));
}
output_stream.end();
@@ -98,5 +99,5 @@ class HLDataRead extends HLFilesystemOperation {
}
module.exports = {
HLDataRead
HLDataRead,
};

View File

@@ -18,12 +18,12 @@
*/
const { chkperm } = require('../../helpers');
const { RootNodeSelector, NodeChildSelector, NodePathSelector } = require("../node/selectors");
const { RootNodeSelector, NodeChildSelector, NodePathSelector } = require('../node/selectors');
const APIError = require('../../api/APIError');
const FSNodeParam = require('../../api/filesystem/FSNodeParam');
const StringParam = require('../../api/filesystem/StringParam');
const FlagParam = require("../../api/filesystem/FlagParam");
const FlagParam = require('../../api/filesystem/FlagParam');
const UserParam = require('../../api/filesystem/UserParam');
const FSNodeContext = require('../FSNodeContext');
const { OtelFeature } = require('../../traits/OtelFeature');
@@ -50,16 +50,16 @@ class MkTree extends HLFilesystemOperation {
├── q
└── r
└── s
`
`;
static PARAMETERS = {
parent: new FSNodeParam('parent', { optional: true }),
}
};
static PROPERTIES = {
leaves: () => [],
directories_created: () => [],
}
};
async _run () {
const { values, context } = this;
@@ -94,31 +94,33 @@ class MkTree extends HLFilesystemOperation {
// This is just a loop that goes through each part of the path
// until it finds the first directory that doesn't exist yet.
let i = 0;
if ( parent_exists ) for ( ; i < dirs.length ; i++ ) {
const dir = dirs[i];
const currentParent = current;
current = new NodeChildSelector(current, dir);
if ( parent_exists ) {
for ( ; i < dirs.length ; i++ ) {
const dir = dirs[i];
const currentParent = current;
current = new NodeChildSelector(current, dir);
const maybe_dir = await fs.node(current);
const maybe_dir = await fs.node(current);
if ( maybe_dir.isRoot ) continue;
if ( await maybe_dir.isUserDirectory() ) continue;
if ( maybe_dir.isRoot ) continue;
if ( await maybe_dir.isUserDirectory() ) continue;
if ( await maybe_dir.exists() ) {
if ( await maybe_dir.exists() ) {
if ( await maybe_dir.get('type') !== FSNodeContext.TYPE_DIRECTORY ) {
throw APIError.create('dest_is_not_a_directory');
if ( await maybe_dir.get('type') !== FSNodeContext.TYPE_DIRECTORY ) {
throw APIError.create('dest_is_not_a_directory');
}
continue;
}
continue;
current = currentParent;
parent_exists = false;
break;
}
current = currentParent;
parent_exists = false;
break;
}
if ( parent_did_exist && ! parent_exists ) {
if ( parent_did_exist && !parent_exists ) {
const node = await fs.node(current);
const has_perm = await chkperm(await node.get('entry'), actor.type.user.id, 'write');
if ( ! has_perm ) throw APIError.create('permission_denied');
@@ -145,7 +147,7 @@ class MkTree extends HLFilesystemOperation {
parent: await fs.node(currentParent),
name: current.name,
actor,
})
});
current = node.selector;
@@ -191,9 +193,7 @@ class QuickMkdir extends HLFilesystemOperation {
currentSpan.setAttribute('parent', parent.selector.describe());
}
for ( let i=0 ; i < dirs.length ; i++ ) {
for ( let i = 0 ; i < dirs.length ; i++ ) {
const dir = dirs[i];
const currentParent = current;
current = new NodeChildSelector(current, dir);
@@ -203,7 +203,7 @@ class QuickMkdir extends HLFilesystemOperation {
parent: await fs.node(currentParent),
name: current.name,
actor,
})
});
current = node.selector;
@@ -224,7 +224,7 @@ class HLMkdir extends HLFilesystemOperation {
- overwrite existing files
- dedupe names
- create shortcuts
`
`;
static PARAMETERS = {
parent: new FSNodeParam('parent', { optional: true }),
@@ -238,18 +238,18 @@ class HLMkdir extends HLFilesystemOperation {
static MODULES = {
_path: require('path'),
}
};
static PROPERTIES = {
parent_directories_created: () => [],
}
};
static FEATURES = [
new OtelFeature([
'_get_existing_parent',
'_create_parents',
]),
]
];
async _run () {
const { context, values } = this;
@@ -290,7 +290,7 @@ class HLMkdir extends HLFilesystemOperation {
if ( top_parent.isRoot ) {
// root directory is read-only
throw APIError.create('forbidden', null, {
message: 'Cannot create directories in the root directory.'
message: 'Cannot create directories in the root directory.',
});
}
@@ -306,9 +306,7 @@ class HLMkdir extends HLFilesystemOperation {
const has_perm = await chkperm(await parent_node.get('entry'), user_id, 'write');
if ( ! has_perm ) throw APIError.create('permission_denied');
const existing = await fs.node(
new NodeChildSelector(parent_node.selector, target_basename)
);
const existing = await fs.node(new NodeChildSelector(parent_node.selector, target_basename));
await existing.fetchEntry();
@@ -328,7 +326,7 @@ class HLMkdir extends HLFilesystemOperation {
else if ( dedupe_name ) {
const fs = context.get('services').get('filesystem');
const parent_selector = parent_node.selector;
for ( let i=1 ;; i++ ) {
for ( let i = 1 ;; i++ ) {
let try_new_name = `${target_basename} (${i})`;
const selector = new NodeChildSelector(parent_selector, try_new_name);
const exists = await parent_node.provider.quick_check({
@@ -411,7 +409,7 @@ class HLMkdir extends HLFilesystemOperation {
let remaining_path = _path.dirname(values.path).split('/').filter(Boolean);
{
const parts = remaining_path.slice();
for (;;) {
for ( ;; ) {
if ( remaining_path.length === 0 ) {
return deepest_existing;
}
@@ -447,7 +445,7 @@ class HLMkdir extends HLFilesystemOperation {
: target_dirname.split('/').filter(Boolean);
let current = parent_node.selector;
for ( let i=0 ; i < dirs.length ; i++ ) {
for ( let i = 0 ; i < dirs.length ; i++ ) {
current = new NodeChildSelector(current, dirs[i]);
}

View File

@@ -16,22 +16,22 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const FSNodeParam = require("../../api/filesystem/FSNodeParam");
const StringParam = require("../../api/filesystem/StringParam");
const { HLFilesystemOperation } = require("./definitions");
const APIError = require("../../api/APIError");
const { TYPE_DIRECTORY } = require("../FSNodeContext");
const FSNodeParam = require('../../api/filesystem/FSNodeParam');
const StringParam = require('../../api/filesystem/StringParam');
const { HLFilesystemOperation } = require('./definitions');
const APIError = require('../../api/APIError');
const { TYPE_DIRECTORY } = require('../FSNodeContext');
class HLMkLink extends HLFilesystemOperation {
static PARAMETERS = {
parent: new FSNodeParam('symlink'),
name: new StringParam('name'),
target: new StringParam('target'),
}
};
static MODULES = {
path: require('node:path'),
}
};
async _run () {
const { context, values } = this;

View File

@@ -16,12 +16,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("../../api/APIError");
const FSNodeParam = require("../../api/filesystem/FSNodeParam");
const FlagParam = require("../../api/filesystem/FlagParam");
const StringParam = require("../../api/filesystem/StringParam");
const { TYPE_DIRECTORY } = require("../FSNodeContext");
const { HLFilesystemOperation } = require("./definitions");
const APIError = require('../../api/APIError');
const FSNodeParam = require('../../api/filesystem/FSNodeParam');
const FlagParam = require('../../api/filesystem/FlagParam');
const StringParam = require('../../api/filesystem/StringParam');
const { TYPE_DIRECTORY } = require('../FSNodeContext');
const { HLFilesystemOperation } = require('./definitions');
class HLMkShortcut extends HLFilesystemOperation {
static PARAMETERS = {
@@ -30,14 +30,14 @@ class HLMkShortcut extends HLFilesystemOperation {
target: new FSNodeParam('target'),
dedupe_name: new FlagParam('dedupe_name', { optional: true }),
}
};
static MODULES = {
path: require('node:path'),
}
};
async _run () {
console.log('HLMKSHORTCUT IS HAPPENING')
console.log('HLMKSHORTCUT IS HAPPENING');
const { context, values } = this;
const fs = context.get('services').get('filesystem');
@@ -50,7 +50,7 @@ class HLMkShortcut extends HLFilesystemOperation {
if ( ! name ) {
dedupe_name = true;
name = 'Shortcut to ' + await target.get('name');
name = `Shortcut to ${ await target.get('name')}`;
}
{
@@ -79,7 +79,7 @@ class HLMkShortcut extends HLFilesystemOperation {
const name_ext = this.modules.path.extname(name);
const name_noext = this.modules.path.basename(name, name_ext);
for ( let i=1 ;; i++ ) {
for ( let i = 1 ;; i++ ) {
const try_new_name = `${name_noext} (${i})${name_ext}`;
const try_dest = await parent.getChild(try_new_name);
if ( ! await try_dest.exists() ) {

View File

@@ -1,26 +1,26 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
*
* This file is part of Puter.
*
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { DB_READ } = require("../../services/database/consts");
const { Context } = require("../../util/context");
const { NodeUIDSelector } = require("../node/selectors");
const { HLFilesystemOperation } = require("./definitions");
const { DB_READ } = require('../../services/database/consts');
const { Context } = require('../../util/context');
const { NodeUIDSelector } = require('../node/selectors');
const { HLFilesystemOperation } = require('./definitions');
class HLNameSearch extends HLFilesystemOperation {
async _run () {
@@ -31,20 +31,18 @@ class HLNameSearch extends HLFilesystemOperation {
.get(DB_READ, 'fs.namesearch');
term = term.replace(/%/g, '');
term = '%' + term + '%';
term = `%${ term }%`;
// Only user actors can do this, because the permission
// system would otherwise slow things down
if ( ! actor.type.user ) return [];
const results = await db.read(
`SELECT uuid FROM fsentries WHERE name LIKE ? AND ` +
`user_id = ? LIMIT 50`,
[term, actor.type.user.id]
);
const results = await db.read('SELECT uuid FROM fsentries WHERE name LIKE ? AND ' +
'user_id = ? LIMIT 50',
[term, actor.type.user.id]);
const uuids = results.map(v => v.uuid);
const fsnodes = await Promise.all(uuids.map(async uuid => {
return await svc_fs.node(new NodeUIDSelector(uuid));
}));

View File

@@ -16,22 +16,22 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("../../api/APIError");
const { LLRead } = require("../ll_operations/ll_read");
const { HLFilesystemOperation } = require("./definitions");
const APIError = require('../../api/APIError');
const { LLRead } = require('../ll_operations/ll_read');
const { HLFilesystemOperation } = require('./definitions');
class HLRead extends HLFilesystemOperation {
static CONCERN = 'filesystem';
static MODULES = {
'stream': require('stream'),
}
};
async _run () {
const {
fsNode, actor,
line_count, byte_count,
offset,
version_id, range
version_id, range,
} = this.values;
if ( ! await fsNode.exists() ) {
@@ -40,12 +40,13 @@ class HLRead extends HLFilesystemOperation {
const ll_read = new LLRead();
let stream = await ll_read.run({
fsNode, actor,
fsNode,
actor,
version_id,
range,
...(byte_count !== undefined ? {
offset: offset ?? 0,
length: byte_count
length: byte_count,
} : {}),
});
@@ -64,8 +65,8 @@ class HLRead extends HLFilesystemOperation {
_wrap_stream_line_count (stream, line_count) {
const readline = require('readline');
const rl = readline.createInterface({
input: stream,
terminal: false
input: stream,
terminal: false,
});
const { PassThrough } = this.modules.stream;
@@ -75,11 +76,11 @@ class HLRead extends HLFilesystemOperation {
let lines_read = 0;
new Promise((resolve, reject) => {
rl.on('line', (line) => {
if(lines_read++ >= line_count){
if ( lines_read++ >= line_count ) {
return rl.close();
}
output_stream.write(lines_read > 1 ? '\r\n' + line : line);
output_stream.write(lines_read > 1 ? `\r\n${ line}` : line);
});
rl.on('error', () => {
console.log('error');
@@ -94,5 +95,5 @@ class HLRead extends HLFilesystemOperation {
}
module.exports = {
HLRead
HLRead,
};

View File

@@ -16,19 +16,19 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("../../api/APIError");
const { Context } = require("../../util/context");
const { stream_to_buffer } = require("../../util/streamutil");
const { ECMAP } = require("../ECMAP");
const { TYPE_DIRECTORY, TYPE_SYMLINK } = require("../FSNodeContext");
const { LLListUsers } = require("../ll_operations/ll_listusers");
const { LLReadDir } = require("../ll_operations/ll_readdir");
const { LLReadShares } = require("../ll_operations/ll_readshares");
const { HLFilesystemOperation } = require("./definitions");
const APIError = require('../../api/APIError');
const { Context } = require('../../util/context');
const { stream_to_buffer } = require('../../util/streamutil');
const { ECMAP } = require('../ECMAP');
const { TYPE_DIRECTORY, TYPE_SYMLINK } = require('../FSNodeContext');
const { LLListUsers } = require('../ll_operations/ll_listusers');
const { LLReadDir } = require('../ll_operations/ll_readdir');
const { LLReadShares } = require('../ll_operations/ll_readshares');
const { HLFilesystemOperation } = require('./definitions');
class HLReadDir extends HLFilesystemOperation {
static CONCERN = 'filesystem';
async _run() {
async _run () {
return ECMAP.arun(async () => {
const ecmap = Context.get(ECMAP.SYMBOL);
ecmap.store_fsNodeContext(this.values.subject);
@@ -61,15 +61,14 @@ class HLReadDir extends HLFilesystemOperation {
}
throw APIError.create('readdir_of_non_directory');
}
let children;
this.log.debug('READDIR',
{
userdir: await subject.isUserDirectory(),
namediff: await subject.get('name') !== user.username
}
);
{
userdir: await subject.isUserDirectory(),
namediff: await subject.get('name') !== user.username,
});
if ( subject.isRoot ) {
const ll_listusers = new LLListUsers();
children = await ll_listusers.run(this.values);
@@ -100,7 +99,7 @@ class HLReadDir extends HLFilesystemOperation {
]);
}
const entry = await child.getSafeEntry();
if ( ! no_thumbs && entry.associated_app ) {
if ( !no_thumbs && entry.associated_app ) {
const svc_appIcon = this.context.get('services').get('app-icon');
const icon_result = await svc_appIcon.get_icon_stream({
app_icon: entry.associated_app.icon,

View File

@@ -16,12 +16,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("../../api/APIError");
const { chkperm } = require("../../helpers");
const { TYPE_DIRECTORY } = require("../FSNodeContext");
const { LLRmDir } = require("../ll_operations/ll_rmdir");
const { LLRmNode } = require("../ll_operations/ll_rmnode");
const { HLFilesystemOperation } = require("./definitions");
const APIError = require('../../api/APIError');
const { chkperm } = require('../../helpers');
const { TYPE_DIRECTORY } = require('../FSNodeContext');
const { LLRmDir } = require('../ll_operations/ll_rmdir');
const { LLRmNode } = require('../ll_operations/ll_rmnode');
const { HLFilesystemOperation } = require('./definitions');
class HLRemove extends HLFilesystemOperation {
static PARAMETERS = {
@@ -29,7 +29,7 @@ class HLRemove extends HLFilesystemOperation {
user: {},
recursive: {},
descendants_only: {},
}
};
async _run () {
const { target, user } = this.values;

View File

@@ -16,16 +16,16 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { Context } = require("../../util/context");
const { HLFilesystemOperation } = require("./definitions");
const { Context } = require('../../util/context');
const { HLFilesystemOperation } = require('./definitions');
const APIError = require('../../api/APIError');
const { ECMAP } = require("../ECMAP");
const { NodeUIDSelector } = require("../node/selectors");
const { ECMAP } = require('../ECMAP');
const { NodeUIDSelector } = require('../node/selectors');
class HLStat extends HLFilesystemOperation {
static MODULES = {
['mime-types']: require('mime-types'),
}
};
async _run () {
return await ECMAP.arun(async () => {
@@ -46,12 +46,12 @@ class HLStat extends HLFilesystemOperation {
return_versions,
return_size,
} = this.values;
const maybe_uid_selector = subject.get_selector_of_type(NodeUIDSelector);
// users created before 2025-07-30 might have fsentries with NULL paths.
// we can remove this check once that is fixed.
const user_unix_ts = Number((''+Date.parse(Context.get('actor')?.type?.user?.timestamp)).slice(0, -3));
const user_unix_ts = Number((`${Date.parse(Context.get('actor')?.type?.user?.timestamp)}`).slice(0, -3));
const paths_are_fine = user_unix_ts >= 1722385593;
if ( maybe_uid_selector || paths_are_fine ) {
@@ -67,7 +67,7 @@ class HLStat extends HLFilesystemOperation {
}
// file not found
if( ! subject.found ) throw APIError.create('subject_does_not_exist');
if ( ! subject.found ) throw APIError.create('subject_does_not_exist');
await subject.fetchOwner();
@@ -80,20 +80,20 @@ class HLStat extends HLFilesystemOperation {
// TODO: why is this specific to stat?
const mime = this.require('mime-types');
const contentType = mime.contentType(subject.entry.name)
const contentType = mime.contentType(subject.entry.name);
subject.entry.type = contentType ? contentType : null;
if (return_size) await subject.fetchSize(user);
if (return_subdomains) await subject.fetchSubdomains(user)
if (return_shares || return_permissions) {
if ( return_size ) await subject.fetchSize(user);
if ( return_subdomains ) await subject.fetchSubdomains(user);
if ( return_shares || return_permissions ) {
await subject.fetchShares();
}
if (return_versions) await subject.fetchVersions();
if ( return_versions ) await subject.fetchVersions();
return await subject.getSafeEntry();
}
}
module.exports = {
HLStat
HLStat,
};

View File

@@ -20,10 +20,10 @@ const _path = require('path');
/**
* Puter paths look like any of the following:
*
*
* Absolute path: /user/dir1/dir2/file
* From UID: AAAA-BBBB-CCCC-DDDD/../a/b/c
*
*
* The difference between an absolute path and a UID-relative path
* is the leading forward-slash character.
*/
@@ -44,9 +44,11 @@ class PuterPath {
this.normUnix = _path.normalize(text);
this.normFlat =
(this.normUnix.endsWith('/') && this.normUnix.length > 1)
? this.normUnix.slice(0, -1) : this.normUnix;
? this.normUnix.slice(0, -1) : this.normUnix;
}
get text () {
return this.text_;
}
get text () { return this.text_; }
isRoot () {
if ( this.normFlat === '/' ) return true;
@@ -61,7 +63,7 @@ class PuterPath {
}
isFromUID () {
return ! this.isAbsolute();
return !this.isAbsolute();
}
get reference () {

View File

@@ -16,10 +16,11 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { BaseOperation } = require("../../services/OperationTraceService");
const { BaseOperation } = require('../../services/OperationTraceService');
class LLFilesystemOperation extends BaseOperation {}
class LLFilesystemOperation extends BaseOperation {
}
module.exports = {
LLFilesystemOperation
LLFilesystemOperation,
};

View File

@@ -23,7 +23,7 @@ class LLCopy extends LLFilesystemOperation {
static MODULES = {
_path: require('path'),
uuidv4: require('uuid').v4,
}
};
async _run () {
const { _path, uuidv4 } = this.modules;
@@ -36,7 +36,7 @@ class LLCopy extends LLFilesystemOperation {
const svc_event = svc.get('event');
const uuid = uuidv4();
const ts = Math.round(Date.now()/1000);
const ts = Math.round(Date.now() / 1000);
this.field('target-uid', uuid);
this.field('source', source.selector.describe());

View File

@@ -16,15 +16,15 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { RootNodeSelector, NodeChildSelector } = require("../node/selectors");
const { LLFilesystemOperation } = require("./definitions");
const { RootNodeSelector, NodeChildSelector } = require('../node/selectors');
const { LLFilesystemOperation } = require('./definitions');
class LLListUsers extends LLFilesystemOperation {
static description = `
List user directories which are relevant to the
current actor.
`;
async _run () {
const { context } = this;
const svc = context.get('services');
@@ -35,19 +35,16 @@ class LLListUsers extends LLFilesystemOperation {
const issuers = await svc_permission.list_user_permission_issuers(user);
const nodes = [];
nodes.push(await svc_fs.node(new NodeChildSelector(
new RootNodeSelector(),
user.username,
)));
nodes.push(await svc_fs.node(new NodeChildSelector(new RootNodeSelector(),
user.username)));
for ( const issuer of issuers ) {
const node = await svc_fs.node(new NodeChildSelector(
new RootNodeSelector(),
issuer.username));
const node = await svc_fs.node(new NodeChildSelector(new RootNodeSelector(),
issuer.username));
nodes.push(node);
}
return nodes;
}
}

View File

@@ -16,12 +16,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { LLFilesystemOperation } = require("./definitions");
const { LLFilesystemOperation } = require('./definitions');
class LLMove extends LLFilesystemOperation {
static MODULES = {
_path: require('path'),
}
};
async _run () {
const { context } = this;

View File

@@ -31,7 +31,7 @@ const checkACLForRead = async (aclService, actor, fsNode, skip = false) => {
if ( skip ) {
return;
}
if ( !await aclService.check(actor, fsNode, 'read') ) {
if ( ! await aclService.check(actor, fsNode, 'read') ) {
throw await aclService.get_safe_acl_error(actor, fsNode, 'read');
}
};
@@ -43,7 +43,7 @@ const typeCheckForRead = async (fsNode) => {
class LLRead extends LLFilesystemOperation {
static CONCERN = 'filesystem';
async _run({ fsNode, no_acl, actor, offset, length, range, version_id } = {}){
async _run ({ fsNode, no_acl, actor, offset, length, range, version_id } = {}) {
// extract services from context
const aclService = Context.get('services').get('acl');
const db = Context.get('services')
@@ -51,7 +51,7 @@ class LLRead extends LLFilesystemOperation {
const fileCacheService = Context.get('services').get('file-cache');
// validate input
if ( !await fsNode.exists() ){
if ( ! await fsNode.exists() ) {
throw APIError.create('subject_does_not_exist');
}
// validate initial node
@@ -86,7 +86,7 @@ class LLRead extends LLFilesystemOperation {
[Date.now() / 1000, await fsNode.get('mysql-id')]);
const ownerId = await fsNode.get('user_id');
const chargedActor = actor? actor: new Actor({
const chargedActor = actor ? actor : new Actor({
type: new UserActorType({
user: await get_user({ id: ownerId }),
}),
@@ -141,7 +141,7 @@ class LLRead extends LLFilesystemOperation {
// Meter ingress
const size = await (async () => {
if ( range ){
if ( range ) {
const match = range.match(/bytes=(\d+)-(\d+)/);
if ( match ) {
const start = parseInt(match[1], 10);
@@ -157,7 +157,7 @@ class LLRead extends LLFilesystemOperation {
meteringService.incrementUsage(chargedActor, 'filesystem:egress:bytes', size);
// cache if whole file read
if ( !has_range ) {
if ( ! has_range ) {
// only cache for non-memoryfs providers
if ( ! (fsNode.provider instanceof MemoryFSProvider) ) {
const res = await fileCacheService.maybe_store(fsNode, stream);

View File

@@ -16,17 +16,17 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("../../api/APIError");
const fsCapabilities = require("../definitions/capabilities");
const { ECMAP } = require("../ECMAP");
const { TYPE_SYMLINK } = require("../FSNodeContext");
const { RootNodeSelector } = require("../node/selectors");
const { NodeUIDSelector, NodeChildSelector } = require("../node/selectors");
const { LLFilesystemOperation } = require("./definitions");
const APIError = require('../../api/APIError');
const fsCapabilities = require('../definitions/capabilities');
const { ECMAP } = require('../ECMAP');
const { TYPE_SYMLINK } = require('../FSNodeContext');
const { RootNodeSelector } = require('../node/selectors');
const { NodeUIDSelector, NodeChildSelector } = require('../node/selectors');
const { LLFilesystemOperation } = require('./definitions');
class LLReadDir extends LLFilesystemOperation {
static CONCERN = 'filesystem';
async _run() {
async _run () {
return ECMAP.arun(async () => {
return await this.__run();
});
@@ -65,10 +65,8 @@ class LLReadDir extends LLFilesystemOperation {
if ( subject.isRoot ) {
if ( ! actor.type.user ) return [];
return [
await svc_fs.node(new NodeChildSelector(
new RootNodeSelector(),
actor.type.user.username,
))
await svc_fs.node(new NodeChildSelector(new RootNodeSelector(),
actor.type.user.username)),
];
}
@@ -76,12 +74,12 @@ class LLReadDir extends LLFilesystemOperation {
// UUID Mode
if ( capabilities.has(fsCapabilities.READDIR_UUID_MODE) ) {
this.checkpoint('readdir uuid mode')
this.checkpoint('readdir uuid mode');
const child_uuids = await subject.provider.readdir({
context,
node: subject,
});
this.checkpoint('after get direct descendants')
this.checkpoint('after get direct descendants');
const children = await Promise.all(child_uuids.map(async uuid => {
return await svc_fs.node(new NodeUIDSelector(uuid));
}));

View File

@@ -16,13 +16,13 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { get_user } = require("../../helpers");
const { MANAGE_PERM_PREFIX } = require("../../services/auth/permissionConts.mjs");
const { PermissionUtil } = require("../../services/auth/permissionUtils.mjs");
const { DB_WRITE } = require("../../services/database/consts");
const { NodeUIDSelector } = require("../node/selectors");
const { LLFilesystemOperation } = require("./definitions");
const { LLReadDir } = require("./ll_readdir");
const { get_user } = require('../../helpers');
const { MANAGE_PERM_PREFIX } = require('../../services/auth/permissionConts.mjs');
const { PermissionUtil } = require('../../services/auth/permissionUtils.mjs');
const { DB_WRITE } = require('../../services/database/consts');
const { NodeUIDSelector } = require('../node/selectors');
const { LLFilesystemOperation } = require('./definitions');
const { LLReadDir } = require('./ll_readdir');
class LLReadShares extends LLFilesystemOperation {
static description = `
@@ -34,7 +34,7 @@ class LLReadShares extends LLFilesystemOperation {
will not be traversed.
`;
async _run() {
async _run () {
const { subject, user, actor } = this.values;
const svc = this.context.get('services');

View File

@@ -16,12 +16,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("../../api/APIError");
const { MemoryFSProvider } = require("../../modules/puterfs/customfs/MemoryFSProvider");
const { ParallelTasks } = require("../../util/otelutil");
const FSNodeContext = require("../FSNodeContext");
const { NodeUIDSelector } = require("../node/selectors");
const { LLFilesystemOperation } = require("./definitions");
const APIError = require('../../api/APIError');
const { MemoryFSProvider } = require('../../modules/puterfs/customfs/MemoryFSProvider');
const { ParallelTasks } = require('../../util/otelutil');
const FSNodeContext = require('../FSNodeContext');
const { NodeUIDSelector } = require('../node/selectors');
const { LLFilesystemOperation } = require('./definitions');
const { LLRmNode } = require('./ll_rmnode');
class LLRmDir extends LLFilesystemOperation {
@@ -32,7 +32,7 @@ class LLRmDir extends LLFilesystemOperation {
actor,
descendants_only,
recursive,
// internal use only - not for clients
ignore_not_empty,
@@ -54,7 +54,7 @@ class LLRmDir extends LLFilesystemOperation {
}
}
if ( await target.get('immutable') && ! descendants_only ) {
if ( await target.get('immutable') && !descendants_only ) {
throw APIError.create('immutable');
}
@@ -64,7 +64,7 @@ class LLRmDir extends LLFilesystemOperation {
node: target,
});
if ( children.length > 0 && ! recursive && ! ignore_not_empty ) {
if ( children.length > 0 && !recursive && !ignore_not_empty ) {
throw APIError.create('not_empty');
}
@@ -72,17 +72,13 @@ class LLRmDir extends LLFilesystemOperation {
const tasks = new ParallelTasks({ tracer, max: max_tasks });
for ( const child_uuid of children ) {
tasks.add(`fs:rm:rm-child`, async () => {
const child_node = await fs.node(
new NodeUIDSelector(child_uuid)
);
tasks.add('fs:rm:rm-child', async () => {
const child_node = await fs.node(new NodeUIDSelector(child_uuid));
const type = await child_node.get('type');
if ( type === FSNodeContext.TYPE_DIRECTORY ) {
const ll_rm = new LLRmDir();
await ll_rm.run({
target: await fs.node(
new NodeUIDSelector(child_uuid),
),
target: await fs.node(new NodeUIDSelector(child_uuid)),
user,
recursive: true,
descendants_only: false,
@@ -92,9 +88,7 @@ class LLRmDir extends LLFilesystemOperation {
} else {
const ll_rm = new LLRmNode();
await ll_rm.run({
target: await fs.node(
new NodeUIDSelector(child_uuid),
),
target: await fs.node(new NodeUIDSelector(child_uuid)),
user,
});
}
@@ -105,23 +99,23 @@ class LLRmDir extends LLFilesystemOperation {
// TODO (xiaochen): consolidate these two branches
if ( target.provider instanceof MemoryFSProvider ) {
await target.provider.rmdir( {
await target.provider.rmdir({
context,
node: target,
options: {
recursive,
descendants_only,
},
} );
});
} else {
if ( ! descendants_only ) {
await target.provider.rmdir( {
await target.provider.rmdir({
context,
node: target,
options: {
ignore_not_empty: true,
},
} );
});
}
}
}

View File

@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { LLFilesystemOperation } = require("./definitions");
const { LLFilesystemOperation } = require('./definitions');
class LLRmNode extends LLFilesystemOperation {
async _run () {
@@ -24,7 +24,7 @@ class LLRmNode extends LLFilesystemOperation {
const { context } = this;
const svc_event = context.get('services').get("event");
const svc_event = context.get('services').get('event');
// Access Control
{

View File

@@ -16,14 +16,14 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { LLFilesystemOperation } = require("./definitions");
const APIError = require("../../api/APIError");
const { LLFilesystemOperation } = require('./definitions');
const APIError = require('../../api/APIError');
/**
* The "overwrite" write operation.
*
*
* This operation is used to write a file to an existing path.
*
*
* @extends LLFilesystemOperation
*/
class LLOWrite extends LLFilesystemOperation {
@@ -59,9 +59,9 @@ class LLOWrite extends LLFilesystemOperation {
/**
* The "non-overwrite" write operation.
*
*
* This operation is used to write a file to a non-existent path.
*
*
* @extends LLFilesystemOperation
*/
class LLCWrite extends LLFilesystemOperation {
@@ -69,7 +69,7 @@ class LLCWrite extends LLFilesystemOperation {
_path: require('path'),
uuidv4: require('uuid').v4,
config: require('../../config.js'),
}
};
/**
* Executes the create operation by writing a new file to the parent directory.

View File

@@ -75,7 +75,7 @@ class NodeUIDSelector extends NodeSelector {
class NodeInternalIDSelector extends NodeSelector {
constructor (service, id, debugInfo) {
super();
super();
this.service = service;
this.id = id;
this.debugInfo = debugInfo;
@@ -91,9 +91,9 @@ class NodeInternalIDSelector extends NodeSelector {
if ( showDebug ) {
return `[db:${this.id}] (${
JSON.stringify(this.debugInfo, null, 2)
})`
})`;
}
return `[db:${this.id}]`
return `[db:${this.id}]`;
}
}
@@ -114,7 +114,7 @@ class NodeChildSelector extends NodeSelector {
}
describe () {
return this.parent.describe() + '/' + this.name;
return `${this.parent.describe() }/${ this.name}`;
}
}
@@ -144,7 +144,7 @@ class NodeRawEntrySelector extends NodeSelector {
constructor (entry) {
super();
// Fix entries from get_descendants
if ( ! entry.uuid && entry.uid ) {
if ( !entry.uuid && entry.uid ) {
entry.uuid = entry.uid;
if ( entry._id ) {
entry.id = entry._id;
@@ -206,7 +206,7 @@ const relativeSelector = (parent, path) => {
}
return selector;
}
};
module.exports = {
NodeSelector,

View File

@@ -16,8 +16,11 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
class NodeFoundState {}
class NodeFoundState {
}
class NodeDoesNotExistState {}
class NodeDoesNotExistState {
}
class NodeInitialState {}
class NodeInitialState {
}

View File

@@ -67,8 +67,8 @@ class UploadProgressTracker {
if ( idx !== -1 ) {
listeners.splice(idx, 1);
}
}
}
},
};
return det;
}

View File

@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { BaseOperation } = require("../../../services/OperationTraceService");
const { BaseOperation } = require('../../../services/OperationTraceService');
/**
* Handles file upload operations to local disk storage.
@@ -58,7 +58,7 @@ class LocalDiskUploadStrategy extends BaseOperation {
on_progress: evt => {
progress_tracker.set_total(file.size);
progress_tracker.set(evt.uploaded);
}
},
});
}
}
@@ -66,7 +66,8 @@ class LocalDiskUploadStrategy extends BaseOperation {
/**
* Hook called after the operation is inserted into the trace.
*/
post_insert () {}
post_insert () {
}
}
/**
@@ -105,7 +106,8 @@ class LocalDiskCopyStrategy extends BaseOperation {
/**
* Hook called after the operation is inserted into the trace.
*/
post_insert () {}
post_insert () {
}
}
/**

View File

@@ -24,7 +24,7 @@ This module contains functions that validate filesystem operations.
/* eslint-disable no-control-regex */
const config = require("../config");
const config = require('../config');
const path_excludes = () => /[\x00-\x1F]/g;
const node_excludes = () => /[/\x00-\x1F]/g;
@@ -38,8 +38,8 @@ const safety_excludes = [
/[\u2066-\u2069]/, // RTL and LTR isolate
/[\u2028-\u2029]/, // line and paragraph separator
/[\uFF01-\uFF5E]/, // fullwidth ASCII
/[\u2060]/, // word joiner
/[\uFEFF]/, // zero width no-break space
/[\u2060]/, // word joiner
/[\uFEFF]/, // zero width no-break space
/[\uFFFE-\uFFFF]/, // non-characters
];
@@ -56,7 +56,7 @@ const is_valid_node_name = function is_valid_node_name (name) {
if ( name_without_dots.length < 1 ) return false;
return true;
}
};
const is_valid_path = function is_valid_path (path, {
no_relative_components,
@@ -69,8 +69,10 @@ const is_valid_path = function is_valid_path (path, {
if ( exclude.test(path) ) return false;
}
if ( ! allow_path_fragment ) if ( path[0] !== '/' && path[0] !== '.' ) {
return false;
if ( ! allow_path_fragment ) {
if ( path[0] !== '/' && path[0] !== '.' ) {
return false;
}
}
if ( no_relative_components ) {
@@ -83,7 +85,7 @@ const is_valid_path = function is_valid_path (path, {
}
return true;
}
};
module.exports = {
is_valid_node_name,

View File

@@ -32,7 +32,7 @@ const surrounding_box = (col, lines, lengths) => {
if ( ! lengths ) {
lengths = lines.map(line => stringLength(line));
}
const probably_docker = (() => {
try {
// I don't know what the value of this is in Docker,

View File

@@ -17,9 +17,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
module.exports = [
{
sz:40,
txt:`&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
{
sz: 40,
txt: `&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&. ,& &&&&&&&&&&&
@@ -34,11 +34,11 @@ txt:`&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&& .&&&&& & &&&&& &&&&&&&&
&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&`
},
{
sz:72,
txt:`&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&`,
},
{
sz: 72,
txt: `&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
@@ -67,5 +67,5 @@ txt:`&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&`,
}
},
];

File diff suppressed because it is too large Load Diff

View File

@@ -18,58 +18,58 @@
*/
const config = require('./config');
function html_footer(options) {
let html = ``;
if(options.show_footer ?? false){
html += `<div class="container">`;
html += `<footer class="row row-cols-5 py-5 my-5 border-top">`;
html += `<div class="col">`;
html += `<a href="/">`;
html += `<img src="/assets/img/logo-128x128.png" style="width:60px; border-radius: 4px; margin-bottom: 10px;">`;
html += `</a>`;
html += `<div><a href="mailto:hi@puter.com">hi@puter.com</a></div>`;
html += `</div>`;
function html_footer (options) {
let html = '';
if ( options.show_footer ?? false ) {
html += '<div class="container">';
html += '<footer class="row row-cols-5 py-5 my-5 border-top">';
html += '<div class="col">';
html += '<a href="/">';
html += '<img src="/assets/img/logo-128x128.png" style="width:60px; border-radius: 4px; margin-bottom: 10px;">';
html += '</a>';
html += '<div><a href="mailto:hi@puter.com">hi@puter.com</a></div>';
html += '</div>';
html += `<div class="col">`;
html += `</div>`;
html += '<div class="col">';
html += '</div>';
html += `<div class="col">`;
html += `</div>`;
html += '<div class="col">';
html += '</div>';
html += `<div class="col">`;
html += `</div>`;
html += '<div class="col">';
html += '</div>';
html += `<div class="col">`;
html += `<h5>Quick Links</h5>`;
html += `<ul class="nav flex-column">`;
html += `<li class="nav-item mb-2"><a href="/" class="nav-link p-0 text-muted">Home</a></li>`;
html += `<li class="nav-item mb-2"><a href="${ config.protocol + '://blog.' + config.domain}" class="nav-link p-0 text-muted">Blog</a></li>`;
html += `<li class="nav-item mb-2"><a href="/login" class="nav-link p-0 text-muted">Log In</a></li>`;
html += `<li class="nav-item mb-2"><a href="/terms" class="nav-link p-0 text-muted">Terms</a></li>`;
html += `<li class="nav-item mb-2"><a href="/privacy" class="nav-link p-0 text-muted">Privacy Policy</a></li>`;
html += `</ul>`;
html += `</div>`;
html += `</footer>`;
// social
html += `<div style="margin-top:20px; padding-bottom: 20px; padding-top:10px; overflow:hidden; border-top:1px solid #CCC;">`
html += `<p class="text-muted" style="float:left;">Puter Technologies Inc. © ${new Date().getFullYear()}</p>`;
html += `<a href="https://github.com/HeyPuter" target="_blank"><img src="/img/logo-github.svg" class="social-media-icon"></a>`;
html += `<a href="https://www.facebook.com/HeyPuter"><img src="/img/logo-facebook.svg" target="_blank" class="social-media-icon"></a>`;
html += `<a href="https://twitter.com/HeyPuter" target="_blank"><img src="/img/logo-twitter.svg" class="social-media-icon"></a>`;
html += `</div>`;
html += `</div>`;
html += `</main>`;
html += '<div class="col">';
html += '<h5>Quick Links</h5>';
html += '<ul class="nav flex-column">';
html += '<li class="nav-item mb-2"><a href="/" class="nav-link p-0 text-muted">Home</a></li>';
html += `<li class="nav-item mb-2"><a href="${ `${config.protocol }://blog.${ config.domain}`}" class="nav-link p-0 text-muted">Blog</a></li>`;
html += '<li class="nav-item mb-2"><a href="/login" class="nav-link p-0 text-muted">Log In</a></li>';
html += '<li class="nav-item mb-2"><a href="/terms" class="nav-link p-0 text-muted">Terms</a></li>';
html += '<li class="nav-item mb-2"><a href="/privacy" class="nav-link p-0 text-muted">Privacy Policy</a></li>';
html += '</ul>';
html += '</div>';
html += '</footer>';
// social
html += '<div style="margin-top:20px; padding-bottom: 20px; padding-top:10px; overflow:hidden; border-top:1px solid #CCC;">';
html += `<p class="text-muted" style="float:left;">Puter Technologies Inc. © ${new Date().getFullYear()}</p>`;
html += '<a href="https://github.com/HeyPuter" target="_blank"><img src="/img/logo-github.svg" class="social-media-icon"></a>';
html += '<a href="https://www.facebook.com/HeyPuter"><img src="/img/logo-facebook.svg" target="_blank" class="social-media-icon"></a>';
html += '<a href="https://twitter.com/HeyPuter" target="_blank"><img src="/img/logo-twitter.svg" class="social-media-icon"></a>';
html += '</div>';
html += '</div>';
html += '</main>';
}
html += `<script>window.page = "${options.page ?? ''}"</script>`;
html += `<script src="/assets/js/jquery-3.6.0/jquery-3.6.0.min.js"></script>`;
if(options.jsfiles && options.jsfiles.length > 0){
options.jsfiles.forEach(jsfile => {
html += `<script src="${jsfile}"></script>`;
});
}
html += `<script src="/assets/js/app.js"></script>`;
html += `</body>`;
return html;
html += `<script>window.page = "${options.page ?? ''}"</script>`;
html += '<script src="/assets/js/jquery-3.6.0/jquery-3.6.0.min.js"></script>';
if ( options.jsfiles && options.jsfiles.length > 0 ) {
options.jsfiles.forEach(jsfile => {
html += `<script src="${jsfile}"></script>`;
});
}
html += '<script src="/assets/js/app.js"></script>';
html += '</body>';
return html;
}
module.exports = html_footer;

View File

@@ -16,24 +16,24 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const config = require('./config')
const {encode} = require('html-entities');
const config = require('./config');
const { encode } = require('html-entities');
function html_head(options) {
let canonical_url = `${config.origin}/${options.page === 'index' ? '' : options.page}`;
let html = ``;
html += `<!doctype html>`;
html += `<html lang="en" id="html-${options.page ?? 'default'}">`;
html += `<head>`;
// meta tags
html += `<meta charset="utf-8">`;
html += `<meta name="viewport" content="width=device-width, initial-scale=1">`;
html += `<meta name="description" content="${encode(options.meta_description ?? '')}">`;
html += `<meta name="referrer" content="no-referrer">`;
// title
html += `<title>${encode(options.title ?? 'Puter')}</title>`;
// favicons
html += `<link rel="apple-touch-icon" sizes="57x57" href="/img/favicons/apple-icon-57x57.png">
function html_head (options) {
let canonical_url = `${config.origin}/${options.page === 'index' ? '' : options.page}`;
let html = '';
html += '<!doctype html>';
html += `<html lang="en" id="html-${options.page ?? 'default'}">`;
html += '<head>';
// meta tags
html += '<meta charset="utf-8">';
html += '<meta name="viewport" content="width=device-width, initial-scale=1">';
html += `<meta name="description" content="${encode(options.meta_description ?? '')}">`;
html += '<meta name="referrer" content="no-referrer">';
// title
html += `<title>${encode(options.title ?? 'Puter')}</title>`;
// favicons
html += `<link rel="apple-touch-icon" sizes="57x57" href="/img/favicons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/img/favicons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/img/favicons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/img/favicons/apple-icon-76x76.png">
@@ -51,58 +51,58 @@ function html_head(options) {
<meta name="msapplication-TileImage" content="/img/favicons/ms-icon-144x144.png">
<meta name="theme-color" content="#ffffff">`;
// Roboto font
html += `<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:black,bold,medium,regular,light,thin">`;
// Roboto font
html += '<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:black,bold,medium,regular,light,thin">';
// canonical link
html += `<link rel="canonical" href="${canonical_url}" />`;
// canonical link
html += `<link rel="canonical" href="${canonical_url}" />`;
// preload images
if(options.page === 'index'){
html += `<link rel="preload" as="image" href="/assets/img/lock.svg"></link>`;
html += `<link rel="preload" as="image" href="/assets/img/screenshot.png"></link>`;
}
// preload images
if ( options.page === 'index' ) {
html += '<link rel="preload" as="image" href="/assets/img/lock.svg"></link>';
html += '<link rel="preload" as="image" href="/assets/img/screenshot.png"></link>';
}
// Facebook meta tags
html += `<meta property="og:url" content="${canonical_url}">`;
html += `<meta property="og:type" content="website">`;
html += `<meta property="og:title" content="${encode(options.title ?? 'Puter')}">`;
html += `<meta property="og:description" content="${encode(config.short_description)}">`;
html += `<meta property="og:image" content="${config.social_card}">`;
// Facebook meta tags
html += `<meta property="og:url" content="${canonical_url}">`;
html += '<meta property="og:type" content="website">';
html += `<meta property="og:title" content="${encode(options.title ?? 'Puter')}">`;
html += `<meta property="og:description" content="${encode(config.short_description)}">`;
html += `<meta property="og:image" content="${config.social_card}">`;
// Twitter meta tags
html += `<meta name="twitter:card" content="">`;
html += `<meta property="twitter:domain" content="${config.domain}">`;
html += `<meta property="twitter:url" content="${canonical_url}">`;
html += `<meta name="twitter:title" content="${encode(options.title ?? 'Puter')}">`;
html += `<meta name="twitter:description" content="${encode(config.short_description)}">`;
html += `<meta name="twitter:image" content="${config.social_card}">`;
// Twitter meta tags
html += '<meta name="twitter:card" content="">';
html += `<meta property="twitter:domain" content="${config.domain}">`;
html += `<meta property="twitter:url" content="${canonical_url}">`;
html += `<meta name="twitter:title" content="${encode(options.title ?? 'Puter')}">`;
html += `<meta name="twitter:description" content="${encode(config.short_description)}">`;
html += `<meta name="twitter:image" content="${config.social_card}">`;
// CSS
html += `<link href="/assets/bootstrap-5.1.3/css/bootstrap.min.css" rel="stylesheet"></link>`;
html += `<link href="/assets/css/style.css" rel="stylesheet"></link>`;
// CSS
html += '<link href="/assets/bootstrap-5.1.3/css/bootstrap.min.css" rel="stylesheet"></link>';
html += '<link href="/assets/css/style.css" rel="stylesheet"></link>';
html += `</head>`;
html += `<body id="body-${options.page ?? 'default'}">`;
if(options.show_navbar ?? false){
html += `<main>`;
html += `<div class="container">`;
html += `<header class="d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom">`;
html +=`<div class="d-flex align-items-center col-md-3 mb-2 mb-md-0 text-dark text-decoration-none">`;
html += `<a href="/" class="text-dark text-decoration-none">`;
html += `<img class="bi me-2 logo" width="40" height="40" role="img" src="/assets/img/logo-128x128.png" style="margin-right: 0 !important;">`;
html += `</a>`;
html +=`</div>`;
html += `<ul class="nav col-12 col-md-auto mb-2 justify-content-center mb-md-0">`;
html += `</ul>`;
html += '</head>';
html += `<body id="body-${options.page ?? 'default'}">`;
if ( options.show_navbar ?? false ) {
html += '<main>';
html += '<div class="container">';
html += '<header class="d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom">';
html += '<div class="d-flex align-items-center col-md-3 mb-2 mb-md-0 text-dark text-decoration-none">';
html += '<a href="/" class="text-dark text-decoration-none">';
html += '<img class="bi me-2 logo" width="40" height="40" role="img" src="/assets/img/logo-128x128.png" style="margin-right: 0 !important;">';
html += '</a>';
html += '</div>';
html += `</header>`;
html += `</div>`;
}
html += ``;
return html;
html += '<ul class="nav col-12 col-md-auto mb-2 justify-content-center mb-md-0">';
html += '</ul>';
html += '</header>';
html += '</div>';
}
html += '';
return html;
}
module.exports = html_head;

View File

@@ -16,18 +16,17 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
"use strict"
'use strict';
const { Kernel } = require("./Kernel");
const CoreModule = require("./CoreModule");
const { CaptchaModule } = require("./modules/captcha/CaptchaModule"); // Add CaptchaModule
const { Kernel } = require('./Kernel');
const CoreModule = require('./CoreModule');
const { CaptchaModule } = require('./modules/captcha/CaptchaModule'); // Add CaptchaModule
const testlaunch = () => {
const k = new Kernel();
k.add_module(new CoreModule());
k.add_module(new CaptchaModule()); // Register the CaptchaModule
k.boot();
}
};
module.exports = { testlaunch };

View File

@@ -1,10 +1,10 @@
const fs = require('fs').promises;
const path = require('path');
async function prependToJSFiles(directory, snippet) {
async function prependToJSFiles (directory, snippet) {
const jsExtensions = new Set(['.js', '.cjs', '.mjs', '.ts']);
async function processDirectory(dir) {
async function processDirectory (dir) {
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
const promises = [];
@@ -14,7 +14,7 @@ async function prependToJSFiles(directory, snippet) {
if ( entry.isDirectory() ) {
// Skip common directories that shouldn't be modified
if ( !shouldSkipDirectory(entry.name) ) {
if ( ! shouldSkipDirectory(entry.name) ) {
promises.push(processDirectory(fullPath));
}
} else if ( entry.isFile() && jsExtensions.has(path.extname(entry.name)) ) {
@@ -30,7 +30,7 @@ async function prependToJSFiles(directory, snippet) {
}
}
function shouldSkipDirectory(dirName) {
function shouldSkipDirectory (dirName) {
const skipDirs = new Set([
'node_modules',
'gui',
@@ -40,7 +40,7 @@ async function prependToJSFiles(directory, snippet) {
return false;
}
async function prependToFile(filePath, snippet) {
async function prependToFile (filePath, snippet) {
try {
const content = await fs.readFile(filePath, 'utf8');
if ( content.startsWith('//!no-prepend') ) return;

View File

@@ -18,35 +18,31 @@
*/
class ArrayUtil extends use.Library {
/**
*
* @param {*} marked_map
* @param {*} subject
*
* @param {*} marked_map
* @param {*} subject
*/
remove_marked_items (marked_map, subject) {
for ( let i=0 ; i < marked_map.length ; i++ ) {
for ( let i = 0 ; i < marked_map.length ; i++ ) {
let ii = marked_map[i];
// track: type check
if ( ! Number.isInteger(ii) ) {
throw new Error(
'marked_map can only contain integers'
);
throw new Error('marked_map can only contain integers');
}
// track: bounds check
if ( ii < 0 && ii >= subject.length ) {
throw new Error(
'each item in `marked_map` must be within that bounds ' +
'of `subject`'
);
throw new Error('each item in `marked_map` must be within that bounds ' +
'of `subject`');
}
}
marked_map.sort((a, b) => b - a);
for ( let i=0 ; i < marked_map.length ; i++ ) {
for ( let i = 0 ; i < marked_map.length ; i++ ) {
let ii = marked_map[i];
subject.splice(ii, 1);
}
return subject;
}
@@ -54,7 +50,8 @@ class ArrayUtil extends use.Library {
// inner indices
{
const subject = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
];
// 0 1 2 3 4 5 6 7
const marked_map = [2, 5];
this.remove_marked_items(marked_map, subject);
@@ -63,27 +60,30 @@ class ArrayUtil extends use.Library {
// left edge
{
const subject = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
];
// 0 1 2 3 4 5 6 7
const marked_map = [0]
const marked_map = [0];
this.remove_marked_items(marked_map, subject);
assert(() => subject.join('') === 'bcdefgh');
}
// right edge
{
const subject = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
];
// 0 1 2 3 4 5 6 7
const marked_map = [7]
const marked_map = [7];
this.remove_marked_items(marked_map, subject);
assert(() => subject.join('') === 'abcdefg');
}
// both edges
{
const subject = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
];
// 0 1 2 3 4 5 6 7
const marked_map = [0, 7]
const marked_map = [0, 7];
this.remove_marked_items(marked_map, subject);
assert(() => subject.join('') === 'bcdefg');
}

View File

@@ -16,79 +16,87 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { whatis } = require("../util/langutil");
const { whatis } = require('../util/langutil');
class LibTypeTagged extends use.Library {
process (o) {
const could_be = whatis(o) === 'object' || Array.isArray(o);
if ( ! could_be ) return {
$: 'error',
code: 'invalid-type',
message: 'should be object or array',
};
if ( ! could_be ) {
return {
$: 'error',
code: 'invalid-type',
message: 'should be object or array',
};
}
const intermediate = this.get_intermediate_(o);
if ( ! intermediate.type ) return {
$: 'error',
code: 'missing-type-param',
message: 'type parameter is missing',
};
if ( ! intermediate.type ) {
return {
$: 'error',
code: 'missing-type-param',
message: 'type parameter is missing',
};
}
return this.intermediate_to_standard_(intermediate);
}
intermediate_to_standard_ (intermediate) {
const out = {};
out.$ = intermediate.type;
for ( const k in intermediate.meta ) {
out['$' + k] = intermediate.meta[k];
out[`$${ k}`] = intermediate.meta[k];
}
for ( const k in intermediate.body ) {
out[k] = intermediate.body[k];
}
return out;
}
get_intermediate_ (o) {
if ( Array.isArray(o) ) {
return this.process_array_(o);
}
if ( o['$'] === '$meta-body' ) {
return this.process_structured_(o);
}
return this.process_standard_(o);
}
process_array_ (a) {
if ( a.length <= 1 || a.length > 3 ) return {
$: 'error',
code: 'invalid-array-length',
message: 'tag-typed arrays should have 1-3 elements',
};
if ( a.length <= 1 || a.length > 3 ) {
return {
$: 'error',
code: 'invalid-array-length',
message: 'tag-typed arrays should have 1-3 elements',
};
}
const [type, body = {}, meta = {}] = a;
return { $: '$', type, body, meta };
}
process_structured_ (o) {
if ( ! o.hasOwnProperty('type') ) return {
$: 'error',
code: 'missing-type-property',
message: 'missing "type" property'
};
if ( ! o.hasOwnProperty('type') ) {
return {
$: 'error',
code: 'missing-type-property',
message: 'missing "type" property',
};
}
return { $: '$', ...o };
}
process_standard_ (o) {
const type = o.$;
const meta = {};
const body = {};
for ( const k in o ) {
if ( k === '$' ) continue;
if ( k.startsWith('$') ) {
@@ -97,7 +105,7 @@ class LibTypeTagged extends use.Library {
body[k] = o[k];
}
}
return { $: '$', type, meta, body };
}
}

View File

@@ -16,9 +16,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("../api/APIError");
const config = require("../config");
const { Context } = require("../util/context");
const APIError = require('../api/APIError');
const config = require('../config');
const { Context } = require('../util/context');
const abuse = options => (req, res, next) => {
if ( config.disable_abuse_checks ) {

View File

@@ -1,32 +1,32 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
*
* This file is part of Puter.
*
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("../api/APIError");
const APIError = require('../api/APIError');
/**
* Creates an anti-CSRF middleware that validates CSRF tokens in incoming requests.
* This middleware protects against Cross-Site Request Forgery attacks by verifying
* that requests contain a valid anti-CSRF token in the request body.
*
*
* @param {Object} options - Configuration options for the middleware
* @returns {Function} Express middleware function that validates CSRF tokens
*
*
* @example
* // Apply anti-CSRF protection to a route
* app.post('/api/secure-endpoint', anticsrf(), (req, res) => {
@@ -46,7 +46,7 @@ const anticsrf = options => async (req, res, next) => {
err.write(res);
return;
}
next();
};

View File

@@ -16,16 +16,18 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
"use strict"
'use strict';
const APIError = require('../api/APIError');
const { UserActorType } = require('../services/auth/Actor');
const auth2 = require('./auth2');
const auth = async (req, res, next)=>{
const auth = async (req, res, next) => {
let auth2_ok = false;
try{
try {
// Delegate to new middleware
await auth2(req, res, () => { auth2_ok = true; });
await auth2(req, res, () => {
auth2_ok = true;
});
if ( ! auth2_ok ) return;
// Everything using the old reference to the auth middleware
@@ -37,9 +39,9 @@ const auth = async (req, res, next)=>{
next();
}
// auth failed
catch(e){
catch (e) {
return res.status(401).send(e);
}
}
};
module.exports = auth
module.exports = auth;

View File

@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const configurable_auth = require("./configurable_auth");
const configurable_auth = require('./configurable_auth');
const auth2 = configurable_auth({ optional: false });

View File

@@ -17,9 +17,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require('../api/APIError');
const config = require("../config");
const { LegacyTokenError } = require("../services/auth/AuthService");
const { Context } = require("../util/context");
const config = require('../config');
const { LegacyTokenError } = require('../services/auth/AuthService');
const { Context } = require('../util/context');
// The "/whoami" endpoint is a special case where we want to allow
// a legacy token to be used for authentication. The "/whoami"
@@ -33,7 +33,7 @@ const is_whoami = (req) => {
// const subdomain = req.subdomains[res.subdomains.length - 1];
// if ( subdomain !== 'api' ) return;
return true;
}
};
// TODO: Allow auth middleware to be used without requiring
// authentication. This will allow us to use the auth middleware
@@ -56,40 +56,48 @@ const configurable_auth = options => async (req, res, next) => {
let token;
// Auth token in body
if(req.body && req.body.auth_token)
if ( req.body && req.body.auth_token )
{
token = req.body.auth_token;
}
// HTTML Auth header
else if (req.header && req.header('Authorization') && !req.header('Authorization').startsWith("Basic ") && req.header('Authorization') !== "Bearer") { // Bearer with no space is something office does
else if ( req.header && req.header('Authorization') && !req.header('Authorization').startsWith('Basic ') && req.header('Authorization') !== 'Bearer' ) { // Bearer with no space is something office does
token = req.header('Authorization');
token = token.replace('Bearer ', '').trim();
if ( token === 'undefined' ) {
APIError.create('unexpected_undefined', null, {
msg: `The Authorization token cannot be the string "undefined"`
msg: 'The Authorization token cannot be the string "undefined"',
});
}
}
// Cookie
else if(req.cookies && req.cookies[config.cookie_name])
else if ( req.cookies && req.cookies[config.cookie_name] )
{
token = req.cookies[config.cookie_name];
}
// Auth token in URL
else if(req.query && req.query.auth_token)
else if ( req.query && req.query.auth_token )
{
token = req.query.auth_token;
}
// Socket
else if(req.handshake && req.handshake.query && req.handshake.query.auth_token)
else if ( req.handshake && req.handshake.query && req.handshake.query.auth_token )
{
token = req.handshake.query.auth_token;
if(!token || token.startsWith("Basic ")) {
}
if ( !token || token.startsWith('Basic ') ) {
if ( optional ) {
next();
return;
}
APIError.create('token_missing').write(res);
return;
} else if (typeof token !== 'string') {
} else if ( typeof token !== 'string' ) {
APIError.create('token_auth_failed').write(res);
return;
} else {
token = token.replace('Bearer ', '')
token = token.replace('Bearer ', '');
}
// === Delegate to AuthService ===
@@ -113,14 +121,14 @@ const configurable_auth = options => async (req, res, next) => {
const new_info = await svc_auth.check_session(token, {
req,
from_upgrade: true,
})
});
context.set('actor', new_info.actor);
context.set('user', new_info.user);
req.new_token = new_info.token;
req.token = new_info.token;
req.user = new_info.user;
req.actor = new_info.actor;
if ( req.user?.suspended ) {
throw APIError.create('forbidden');
}

View File

@@ -1,28 +1,28 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
*
* This file is part of Puter.
*
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("../api/APIError");
const { Context } = require("../util/context");
const APIError = require('../api/APIError');
const { Context } = require('../util/context');
const featureflag = options => async (req, res, next) => {
const { feature } = options;
const context = Context.get();
const services = context.get('services');
const svc_featureFlag = services.get('feature-flag');

Some files were not shown because too many files have changed in this diff Show More