';
+ }
+
+ html += '';
+ return html;
}
module.exports = html_head;
\ No newline at end of file
diff --git a/src/backend/src/index.js b/src/backend/src/index.js
index 1ff54e332c..3699ce65a8 100644
--- a/src/backend/src/index.js
+++ b/src/backend/src/index.js
@@ -16,18 +16,17 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-"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 };
diff --git a/src/backend/src/kernel/modutil.js b/src/backend/src/kernel/modutil.js
index a719a8a45c..8475cf2a2e 100644
--- a/src/backend/src/kernel/modutil.js
+++ b/src/backend/src/kernel/modutil.js
@@ -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;
diff --git a/src/backend/src/libraries/ArrayUtil.js b/src/backend/src/libraries/ArrayUtil.js
index c9a70c9281..18d01f4fb6 100644
--- a/src/backend/src/libraries/ArrayUtil.js
+++ b/src/backend/src/libraries/ArrayUtil.js
@@ -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');
}
diff --git a/src/backend/src/libraries/LibTypeTagged.js b/src/backend/src/libraries/LibTypeTagged.js
index 4ff0feeae5..707cce176e 100644
--- a/src/backend/src/libraries/LibTypeTagged.js
+++ b/src/backend/src/libraries/LibTypeTagged.js
@@ -16,79 +16,87 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-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 };
}
}
diff --git a/src/backend/src/middleware/abuse.js b/src/backend/src/middleware/abuse.js
index 5f718686c3..81c2987fc8 100644
--- a/src/backend/src/middleware/abuse.js
+++ b/src/backend/src/middleware/abuse.js
@@ -16,9 +16,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-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 ) {
diff --git a/src/backend/src/middleware/anticsrf.js b/src/backend/src/middleware/anticsrf.js
index e7844c577e..d983e4a3fc 100644
--- a/src/backend/src/middleware/anticsrf.js
+++ b/src/backend/src/middleware/anticsrf.js
@@ -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 .
*/
-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();
};
diff --git a/src/backend/src/middleware/auth.js b/src/backend/src/middleware/auth.js
index ff2a37bae7..977eb8a318 100644
--- a/src/backend/src/middleware/auth.js
+++ b/src/backend/src/middleware/auth.js
@@ -16,16 +16,18 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-"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
\ No newline at end of file
+module.exports = auth;
\ No newline at end of file
diff --git a/src/backend/src/middleware/auth2.js b/src/backend/src/middleware/auth2.js
index a739979c27..bcd93591cd 100644
--- a/src/backend/src/middleware/auth2.js
+++ b/src/backend/src/middleware/auth2.js
@@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const configurable_auth = require("./configurable_auth");
+const configurable_auth = require('./configurable_auth');
const auth2 = configurable_auth({ optional: false });
diff --git a/src/backend/src/middleware/configurable_auth.js b/src/backend/src/middleware/configurable_auth.js
index 9895f2082b..1195614526 100644
--- a/src/backend/src/middleware/configurable_auth.js
+++ b/src/backend/src/middleware/configurable_auth.js
@@ -17,9 +17,9 @@
* along with this program. If not, see .
*/
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');
}
diff --git a/src/backend/src/middleware/featureflag.js b/src/backend/src/middleware/featureflag.js
index 316908e949..836efa44ed 100644
--- a/src/backend/src/middleware/featureflag.js
+++ b/src/backend/src/middleware/featureflag.js
@@ -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 .
*/
-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');
diff --git a/src/backend/src/middleware/measure.js b/src/backend/src/middleware/measure.js
index a48220a980..ce43bca15c 100644
--- a/src/backend/src/middleware/measure.js
+++ b/src/backend/src/middleware/measure.js
@@ -15,14 +15,14 @@ const _intercept_req = ({ data, req, next }) => {
const replaces = ['readable', 'pipe', 'on', 'once', 'removeListener'];
for ( const replace of replaces ) {
- const replacement = req_pass[replace]
+ const replacement = req_pass[replace];
Object.defineProperty(req, replace, {
get () {
if ( typeof replacement === 'function' ) {
return replacement.bind(req_pass);
}
return replacement;
- }
+ },
});
}
} catch (e) {
@@ -39,28 +39,28 @@ const _intercept_res = ({ data, res, next }) => {
try {
const org_write = res.write;
const org_end = res.end;
-
+
// Override the `write` method
res.write = function (chunk, ...args) {
- if (Buffer.isBuffer(chunk)) {
- data.sz_outgoing += chunk.length;
- } else if (typeof chunk === 'string') {
- data.sz_outgoing += Buffer.byteLength(chunk);
- }
- return org_write.apply(res, [chunk, ...args]);
+ if ( Buffer.isBuffer(chunk) ) {
+ data.sz_outgoing += chunk.length;
+ } else if ( typeof chunk === 'string' ) {
+ data.sz_outgoing += Buffer.byteLength(chunk);
+ }
+ return org_write.apply(res, [chunk, ...args]);
};
-
+
// Override the `end` method
res.end = function (chunk, ...args) {
- if (chunk) {
- if (Buffer.isBuffer(chunk)) {
- data.sz_outgoing += chunk.length;
- } else if (typeof chunk === 'string') {
- data.sz_outgoing += Buffer.byteLength(chunk);
+ if ( chunk ) {
+ if ( Buffer.isBuffer(chunk) ) {
+ data.sz_outgoing += chunk.length;
+ } else if ( typeof chunk === 'string' ) {
+ data.sz_outgoing += Buffer.byteLength(chunk);
+ }
}
- }
- const result = org_end.apply(res, [chunk, ...args]);
- return result;
+ const result = org_end.apply(res, [chunk, ...args]);
+ return result;
};
} catch (e) {
console.error(e);
diff --git a/src/backend/src/middleware/subdomain.js b/src/backend/src/middleware/subdomain.js
index 048ef60d6a..d578c4aa6f 100644
--- a/src/backend/src/middleware/subdomain.js
+++ b/src/backend/src/middleware/subdomain.js
@@ -21,10 +21,10 @@
* match it calls `next('route')` to skip the current route.
* Be sure to use this before any middleware that might erroneously
* block the request.
- *
+ *
* @param {string|string[]} allowedSubdomains - The subdomain to allow;
* if an array, any of the subdomains in the array will be allowed.
- *
+ *
* @returns {function} - An express middleware function
*/
const subdomain = allowedSubdomains => {
@@ -43,6 +43,6 @@ const subdomain = allowedSubdomains => {
next();
};
-}
+};
module.exports = subdomain;
diff --git a/src/backend/src/middleware/verified.js b/src/backend/src/middleware/verified.js
index f0a81b66f0..da66f91d27 100644
--- a/src/backend/src/middleware/verified.js
+++ b/src/backend/src/middleware/verified.js
@@ -16,9 +16,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const config = require("../config")
+const config = require('../config');
-const verified = async (req, res, next)=>{
+const verified = async (req, res, next) => {
if ( ! config.strict_email_verification_required ) {
next();
return;
@@ -36,8 +36,8 @@ const verified = async (req, res, next)=>{
res.status(400).send({
code: 'account_is_not_verified',
- message: 'Account is not verified'
+ message: 'Account is not verified',
});
-}
+};
-module.exports = verified
+module.exports = verified;
diff --git a/src/backend/src/modules/apps/AppIconService.js b/src/backend/src/modules/apps/AppIconService.js
index a9765008b5..ed43b12a77 100644
--- a/src/backend/src/modules/apps/AppIconService.js
+++ b/src/backend/src/modules/apps/AppIconService.js
@@ -1,43 +1,43 @@
/*
* 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 .
*/
-const { HLWrite } = require("../../filesystem/hl_operations/hl_write");
-const { LLMkdir } = require("../../filesystem/ll_operations/ll_mkdir");
-const { LLRead } = require("../../filesystem/ll_operations/ll_read");
-const { NodePathSelector } = require("../../filesystem/node/selectors");
-const { get_app } = require("../../helpers");
-const { Endpoint } = require("../../util/expressutil");
-const { buffer_to_stream, stream_to_buffer } = require("../../util/streamutil");
-const BaseService = require("../../services/BaseService.js");
+const { HLWrite } = require('../../filesystem/hl_operations/hl_write');
+const { LLMkdir } = require('../../filesystem/ll_operations/ll_mkdir');
+const { LLRead } = require('../../filesystem/ll_operations/ll_read');
+const { NodePathSelector } = require('../../filesystem/node/selectors');
+const { get_app } = require('../../helpers');
+const { Endpoint } = require('../../util/expressutil');
+const { buffer_to_stream, stream_to_buffer } = require('../../util/streamutil');
+const BaseService = require('../../services/BaseService.js');
-const ICON_SIZES = [16,32,64,128,256,512];
+const ICON_SIZES = [16, 32, 64, 128, 256, 512];
const DEFAULT_APP_ICON = require('./default-app-icon.js');
-const IconResult = require("./lib/IconResult.js");
+const IconResult = require('./lib/IconResult.js');
/**
* AppIconService handles icon generation and serving for apps.
- *
+ *
* This is done by listening to the `app.new-icon` event which is
* dispatched by AppES. `sharp` is used to resize the images to
* pre-selected sizees in the `ICON_SIZES` constant defined above.
- *
+ *
* Icons are stored in and served from the `/system/app_icons`
* directory. If the system user does not have this directory,
* it will be created in the consolidation boot phase after
@@ -49,8 +49,8 @@ class AppIconService extends BaseService {
sharp: require('sharp'),
bmp: require('sharp-bmp'),
ico: require('sharp-ico'),
- }
-
+ };
+
static ICON_SIZES = ICON_SIZES;
/**
@@ -76,18 +76,18 @@ class AppIconService extends BaseService {
const {
stream,
mime,
- } = await this.get_icon_stream({ app_uid, size, })
+ } = await this.get_icon_stream({ app_uid, size });
res.set('Content-Type', mime);
stream.pipe(res);
},
}).attach(app);
}
-
+
get_sizes () {
return this.constructor.ICON_SIZES;
}
-
+
async iconify_apps ({ apps, size }) {
return await Promise.all(apps.map(async app => {
const icon_result = await this.get_icon_stream({
@@ -104,7 +104,7 @@ class AppIconService extends BaseService {
try {
const buffer = await stream_to_buffer(icon_result.stream);
const resp_data_url = `data:${icon_result.mime};base64,${buffer.toString('base64')}`;
-
+
app.icon = resp_data_url;
} catch (e) {
this.errors.report('get-launch-apps:icon-stream', {
@@ -127,14 +127,14 @@ class AppIconService extends BaseService {
const input_mime = metadata.split(';')[0].split(':')[1];
// svg icons will be sent as-is
- if (input_mime === 'image/svg+xml') {
+ if ( input_mime === 'image/svg+xml' ) {
return {
mime: 'image/svg+xml',
get stream () {
return buffer_to_stream(Buffer.from(data, 'base64'));
},
data_url: app_icon,
- }
+ };
}
}
@@ -147,7 +147,7 @@ class AppIconService extends BaseService {
app_icon = app_icon || await (async () => {
const app = await get_app({ uid: app_uid });
return app.icon || DEFAULT_APP_ICON;
- })()
+ })();
const [metadata, base64] = app_icon.split(',');
const mime = metadata.split(';')[0].split(':')[1];
const img = Buffer.from(base64, 'base64');
@@ -155,7 +155,7 @@ class AppIconService extends BaseService {
mime,
stream: buffer_to_stream(img),
};
- }
+ };
if ( ! await node.exists() ) {
return await get_fallback_icon();
@@ -169,7 +169,7 @@ class AppIconService extends BaseService {
stream: await ll_read.run({
fsNode: node,
actor: await svc_su.get_system_actor(),
- })
+ }),
};
} catch (e) {
this.errors.report('AppIconService.get_icon_stream', {
@@ -187,7 +187,7 @@ class AppIconService extends BaseService {
// let second_size = size > 16 ? size / 2 : 32;
return await this.get_icon_stream({
- app_uid, size: second_size, tries: tries + 1
+ app_uid, size: second_size, tries: tries + 1,
});
}
return await get_fallback_icon();
@@ -204,9 +204,7 @@ class AppIconService extends BaseService {
}
const svc_fs = this.services.get('filesystem');
- const dir_app_icons = await svc_fs.node(
- new NodePathSelector('/system/app_icons')
- );
+ const dir_app_icons = await svc_fs.node(new NodePathSelector('/system/app_icons'));
return this.dir_app_icons = dir_app_icons;
}
@@ -226,7 +224,7 @@ class AppIconService extends BaseService {
return this.modules.sharp(input);
}
-
+
/**
* AppIconService listens to this event to create the
* `/system/app_icons` directory if it does not exist,
@@ -240,9 +238,7 @@ class AppIconService extends BaseService {
const dir_system = await svc_user.get_system_dir();
// Ensure app icons directory exists
- const dir_app_icons = await svc_fs.node(
- new NodePathSelector('/system/app_icons')
- );
+ const dir_app_icons = await svc_fs.node(new NodePathSelector('/system/app_icons'));
if ( ! await dir_app_icons.exists() ) {
const ll_mkdir = new LLMkdir();
await ll_mkdir.run({
@@ -278,7 +274,7 @@ class AppIconService extends BaseService {
metadata,
input,
});
-
+
// NOTE: A stream would be more ideal than a buffer here
// but we have no way of knowing the output size
// before we finish processing the image.
@@ -286,7 +282,7 @@ class AppIconService extends BaseService {
.resize(size)
.png()
.toBuffer();
-
+
const sys_actor = await svc_su.get_system_actor();
const hl_write = new HLWrite();
await hl_write.run({
@@ -304,7 +300,7 @@ class AppIconService extends BaseService {
stream: buffer_to_stream(output),
},
});
- })
+ });
})());
}
await Promise.all(icon_jobs);
diff --git a/src/backend/src/modules/apps/AppInformationService.js b/src/backend/src/modules/apps/AppInformationService.js
index d4e38798ea..f6d25cd387 100644
--- a/src/backend/src/modules/apps/AppInformationService.js
+++ b/src/backend/src/modules/apps/AppInformationService.js
@@ -18,9 +18,9 @@
* along with this program. If not, see .
*/
const { asyncSafeSetInterval } = require('@heyputer/putility').libs.promise;
-const { MINUTE } = require("@heyputer/putility").libs.time;
-const { origin_from_url } = require("../../util/urlutil");
-const { DB_READ } = require("../../services/database/consts");
+const { MINUTE } = require('@heyputer/putility').libs.time;
+const { origin_from_url } = require('../../util/urlutil');
+const { DB_READ } = require('../../services/database/consts');
const BaseService = require('../../services/BaseService');
// Currently leaks memory (not sure why yet, but icons are a factor)
@@ -51,19 +51,19 @@ class AppInformationService extends BaseService {
'day': '%Y-%m-%d',
'week': '%Y-%U',
'month': '%Y-%m',
- 'year': '%Y'
+ 'year': '%Y',
};
// ClickHouse date format mapping for different groupings
this.clickhouseGroupByFormats = {
- 'hour': "toStartOfHour(fromUnixTimestamp(ts))",
- 'day': "toStartOfDay(fromUnixTimestamp(ts))",
- 'week': "toStartOfWeek(fromUnixTimestamp(ts))",
- 'month': "toStartOfMonth(fromUnixTimestamp(ts))",
- 'year': "toStartOfYear(fromUnixTimestamp(ts))"
+ 'hour': 'toStartOfHour(fromUnixTimestamp(ts))',
+ 'day': 'toStartOfDay(fromUnixTimestamp(ts))',
+ 'week': 'toStartOfWeek(fromUnixTimestamp(ts))',
+ 'month': 'toStartOfMonth(fromUnixTimestamp(ts))',
+ 'year': 'toStartOfYear(fromUnixTimestamp(ts))',
};
}
-
+
['__on_boot.consolidation'] () {
(async () => {
// await new Promise(rslv => setTimeout(rslv, 500))
@@ -72,7 +72,7 @@ class AppInformationService extends BaseService {
await this._refresh_app_cache();
/**
* Refreshes the application cache by querying the database for all apps and updating the key-value store.
- *
+ *
* This method is called periodically to ensure that the in-memory cache reflects the latest
* state from the database. It uses the 'database' service to fetch app data and then updates
* multiple cache entries for quick lookups by name, ID, and UID.
@@ -88,7 +88,7 @@ class AppInformationService extends BaseService {
/**
* Refreshes the cache of recently opened apps.
* This method updates the 'recent' collection with the UIDs of apps sorted by their most recent timestamp.
- *
+ *
* @async
* @returns {Promise} A promise that resolves when the cache has been refreshed.
*/
@@ -102,7 +102,7 @@ class AppInformationService extends BaseService {
* Refreshes the app referral statistics.
* This method is computationally expensive and thus runs less frequently.
* It queries the database for user counts referred by each app's origin URL.
- *
+ *
* @async
*/
asyncSafeSetInterval(async () => {
@@ -114,7 +114,7 @@ class AppInformationService extends BaseService {
* Refreshes the recent cache by updating the list of recently added or updated apps.
* This method fetches all app data, filters for approved apps, sorts them by timestamp,
* and updates the `this.collections.recent` array with the UIDs of the most recent 50 apps.
- *
+ *
* @async
* @private
*/
@@ -134,14 +134,13 @@ class AppInformationService extends BaseService {
*/
asyncSafeSetInterval(async () => {
this._refresh_tags();
- } , 120 * 1000);
+ }, 120 * 1000);
})();
}
-
/**
* Retrieves and returns statistical data for a specific application over different time periods.
- *
+ *
* This method fetches various metrics such as the number of times the app has been opened,
* the count of unique users who have opened the app, and the number of referrals attributed to the app.
* It supports different time periods such as today, yesterday, past 7 days, past 30 days, and all time.
@@ -155,13 +154,13 @@ class AppInformationService extends BaseService {
* - {Object} user_count - Uniqu>e user counts for different time periods
* - {number|null} referral_count - The number of referrals (all-time only)
*/
- async get_stats(app_uid, options = {}) {
+ async get_stats (app_uid, options = {}) {
let period = options.period ?? 'all';
let stats_grouping = options.grouping;
let app_creation_ts = options.created_at;
// Check cache first if period is 'all' and no grouping is requested
- if (period === 'all' && !stats_grouping) {
+ if ( period === 'all' && !stats_grouping ) {
const key_open_count = `apps:open_count:uid:${app_uid}`;
const key_user_count = `apps:user_count:uid:${app_uid}`;
const key_referral_count = `apps:referral_count:uid:${app_uid}`;
@@ -169,14 +168,14 @@ class AppInformationService extends BaseService {
const [cached_open_count, cached_user_count, cached_referral_count] = await Promise.all([
kv.get(key_open_count),
kv.get(key_user_count),
- kv.get(key_referral_count)
+ kv.get(key_referral_count),
]);
- if (cached_open_count !== null && cached_user_count !== null) {
+ if ( cached_open_count !== null && cached_user_count !== null ) {
return {
open_count: parseInt(cached_open_count),
user_count: parseInt(cached_user_count),
- referral_count: cached_referral_count
+ referral_count: cached_referral_count,
};
}
}
@@ -187,123 +186,121 @@ class AppInformationService extends BaseService {
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
- switch(period) {
- case 'today':
- return {
- start: today.getTime(),
- end: now.getTime()
- };
- case 'yesterday': {
- const yesterday = new Date(today);
- yesterday.setDate(yesterday.getDate() - 1);
- return {
- start: yesterday.getTime(),
- end: today.getTime() - 1
- };
- }
- case '7d': {
- const weekAgo = new Date(now);
- weekAgo.setDate(weekAgo.getDate() - 7);
- return {
- start: weekAgo.getTime(),
- end: now.getTime()
- };
- }
- case '30d': {
- const monthAgo = new Date(now);
- monthAgo.setDate(monthAgo.getDate() - 30);
- return {
- start: monthAgo.getTime(),
- end: now.getTime()
- };
- }
- case 'this_week': {
- const firstDayOfWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay());
- return {
- start: firstDayOfWeek.getTime(),
- end: now.getTime()
- };
- }
- case 'last_week': {
- const firstDayOfLastWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay() - 7);
- const firstDayOfThisWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay());
- return {
- start: firstDayOfLastWeek.getTime(),
- end: firstDayOfThisWeek.getTime() - 1
- };
- }
- case 'this_month': {
- const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
- return {
- start: firstDayOfMonth.getTime(),
- end: now.getTime()
- };
- }
- case 'last_month': {
- const firstDayOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
- const firstDayOfThisMonth = new Date(now.getFullYear(), now.getMonth(), 1);
- return {
- start: firstDayOfLastMonth.getTime(),
- end: firstDayOfThisMonth.getTime() - 1
- };
- }
- case 'this_year': {
- const firstDayOfYear = new Date(now.getFullYear(), 0, 1);
- return {
- start: firstDayOfYear.getTime(),
- end: now.getTime()
- };
- }
- case 'last_year': {
- const firstDayOfLastYear = new Date(now.getFullYear() - 1, 0, 1);
- const firstDayOfThisYear = new Date(now.getFullYear(), 0, 1);
- return {
- start: firstDayOfLastYear.getTime(),
- end: firstDayOfThisYear.getTime() - 1
- };
- }
- case '12m': {
- const twelveMonthsAgo = new Date(now);
- twelveMonthsAgo.setMonth(twelveMonthsAgo.getMonth() - 12);
- return {
- start: twelveMonthsAgo.getTime(),
- end: now.getTime()
- };
- }
- case 'all':{
- const start = new Date(app_creation_ts);
- console.log('NARIMAN', start.getTime(), now.getTime());
- return {
- start: start.getTime(),
- end: now.getTime()
- };
- }
- default:
- return null;
+ switch ( period ) {
+ case 'today':
+ return {
+ start: today.getTime(),
+ end: now.getTime(),
+ };
+ case 'yesterday': {
+ const yesterday = new Date(today);
+ yesterday.setDate(yesterday.getDate() - 1);
+ return {
+ start: yesterday.getTime(),
+ end: today.getTime() - 1,
+ };
+ }
+ case '7d': {
+ const weekAgo = new Date(now);
+ weekAgo.setDate(weekAgo.getDate() - 7);
+ return {
+ start: weekAgo.getTime(),
+ end: now.getTime(),
+ };
+ }
+ case '30d': {
+ const monthAgo = new Date(now);
+ monthAgo.setDate(monthAgo.getDate() - 30);
+ return {
+ start: monthAgo.getTime(),
+ end: now.getTime(),
+ };
+ }
+ case 'this_week': {
+ const firstDayOfWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay());
+ return {
+ start: firstDayOfWeek.getTime(),
+ end: now.getTime(),
+ };
+ }
+ case 'last_week': {
+ const firstDayOfLastWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay() - 7);
+ const firstDayOfThisWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay());
+ return {
+ start: firstDayOfLastWeek.getTime(),
+ end: firstDayOfThisWeek.getTime() - 1,
+ };
+ }
+ case 'this_month': {
+ const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
+ return {
+ start: firstDayOfMonth.getTime(),
+ end: now.getTime(),
+ };
+ }
+ case 'last_month': {
+ const firstDayOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
+ const firstDayOfThisMonth = new Date(now.getFullYear(), now.getMonth(), 1);
+ return {
+ start: firstDayOfLastMonth.getTime(),
+ end: firstDayOfThisMonth.getTime() - 1,
+ };
+ }
+ case 'this_year': {
+ const firstDayOfYear = new Date(now.getFullYear(), 0, 1);
+ return {
+ start: firstDayOfYear.getTime(),
+ end: now.getTime(),
+ };
+ }
+ case 'last_year': {
+ const firstDayOfLastYear = new Date(now.getFullYear() - 1, 0, 1);
+ const firstDayOfThisYear = new Date(now.getFullYear(), 0, 1);
+ return {
+ start: firstDayOfLastYear.getTime(),
+ end: firstDayOfThisYear.getTime() - 1,
+ };
+ }
+ case '12m': {
+ const twelveMonthsAgo = new Date(now);
+ twelveMonthsAgo.setMonth(twelveMonthsAgo.getMonth() - 12);
+ return {
+ start: twelveMonthsAgo.getTime(),
+ end: now.getTime(),
+ };
+ }
+ case 'all':{
+ const start = new Date(app_creation_ts);
+ console.log('NARIMAN', start.getTime(), now.getTime());
+ return {
+ start: start.getTime(),
+ end: now.getTime(),
+ };
+ }
+ default:
+ return null;
}
};
const timeRange = getTimeRange(period);
// Handle time-based grouping if stats_grouping is specified
- if (stats_grouping) {
+ if ( stats_grouping ) {
const timeFormat = this.mysqlDateFormats[stats_grouping];
- if (!timeFormat) {
+ if ( ! timeFormat ) {
throw new Error(`Invalid stats_grouping: ${stats_grouping}. Supported values are: hour, day, week, month, year`);
}
// Generate all periods for the time range
- const allPeriods = this.generateAllPeriods(
- new Date(timeRange.start),
- new Date(timeRange.end),
- stats_grouping
- );
+ const allPeriods = this.generateAllPeriods(new Date(timeRange.start),
+ new Date(timeRange.end),
+ stats_grouping);
- if (global.clickhouseClient) {
+ if ( global.clickhouseClient ) {
const groupByFormat = this.clickhouseGroupByFormats[stats_grouping];
- const timeCondition = timeRange ?
- `AND ts >= ${Math.floor(timeRange.start/1000)} AND ts < ${Math.floor(timeRange.end/1000)}` : '';
-
+ const timeCondition = timeRange ?
+ `AND ts >= ${Math.floor(timeRange.start / 1000)} AND ts < ${Math.floor(timeRange.end / 1000)}` : '';
+
const [openResult, userResult] = await Promise.all([
global.clickhouseClient.query({
query: `
@@ -316,7 +313,7 @@ class AppInformationService extends BaseService {
GROUP BY period
ORDER BY period
`,
- format: 'JSONEachRow'
+ format: 'JSONEachRow',
}),
global.clickhouseClient.query({
query: `
@@ -329,63 +326,61 @@ class AppInformationService extends BaseService {
GROUP BY period
ORDER BY period
`,
- format: 'JSONEachRow'
- })
+ format: 'JSONEachRow',
+ }),
]);
-
+
const openRows = await openResult.json();
const userRows = await userResult.json();
-
+
// Ensure counts are properly parsed as integers
const processedOpenRows = openRows.map(row => ({
period: new Date(row.period),
- count: parseInt(row.count)
+ count: parseInt(row.count),
}));
-
+
const processedUserRows = userRows.map(row => ({
period: new Date(row.period),
- count: parseInt(row.count)
+ count: parseInt(row.count),
}));
-
+
// Calculate totals from the processed rows
const totalOpenCount = processedOpenRows.reduce((sum, row) => sum + row.count, 0);
const totalUserCount = processedUserRows.reduce((sum, row) => sum + row.count, 0);
-
+
// Generate all periods and merge with actual data
- const allPeriods = this.generateAllPeriods(
- new Date(timeRange.start),
- new Date(timeRange.end),
- stats_grouping
- );
-
+ const allPeriods = this.generateAllPeriods(new Date(timeRange.start),
+ new Date(timeRange.end),
+ stats_grouping);
+
const completeOpenStats = this.mergeWithGeneratedPeriods(processedOpenRows, allPeriods, stats_grouping);
const completeUserStats = this.mergeWithGeneratedPeriods(processedUserRows, allPeriods, stats_grouping);
-
+
return {
open_count: totalOpenCount,
user_count: totalUserCount,
grouped_stats: {
open_count: completeOpenStats,
- user_count: completeUserStats
+ user_count: completeUserStats,
},
- referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null
+ referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null,
};
}
-
+
else {
// MySQL queries for grouped stats
- const queryParams = timeRange ?
- [app_uid, timeRange.start/1000, timeRange.end/1000] :
+ const queryParams = timeRange ?
+ [app_uid, timeRange.start / 1000, timeRange.end / 1000] :
[app_uid];
const [openResult, userResult] = await Promise.all([
db.read(`
- SELECT ` +
+ SELECT ${
db.case({
mysql: `DATE_FORMAT(FROM_UNIXTIME(ts/1000), '${timeFormat}') as period, `,
sqlite: `STRFTIME('%Y-%m-%d %H', datetime(ts/1000, 'unixepoch'), '${timeFormat}') as period, `,
- }) +
- `
+ })
+ }
COUNT(_id) as count
FROM app_opens
WHERE app_uid = ?
@@ -394,19 +389,19 @@ class AppInformationService extends BaseService {
ORDER BY period
`, queryParams),
db.read(`
- SELECT ` +
+ SELECT ${
db.case({
mysql: `DATE_FORMAT(FROM_UNIXTIME(ts/1000), '${timeFormat}') as period, `,
sqlite: `STRFTIME('%Y-%m-%d %H', datetime(ts/1000, 'unixepoch'), '${timeFormat}') as period, `,
- }) +
- `
+ })
+ }
COUNT(DISTINCT user_id) as count
FROM app_opens
WHERE app_uid = ?
${timeRange ? 'AND ts >= ? AND ts < ?' : ''}
GROUP BY period
ORDER BY period
- `, queryParams)
+ `, queryParams),
]);
// Calculate totals
@@ -416,11 +411,11 @@ class AppInformationService extends BaseService {
// Convert MySQL results to the same format as needed
const openRows = openResult.map(row => ({
period: row.period,
- count: parseInt(row.count)
+ count: parseInt(row.count),
}));
const userRows = userResult.map(row => ({
period: row.period,
- count: parseInt(row.count)
+ count: parseInt(row.count),
}));
// Merge with generated periods to include zero-value periods
@@ -432,40 +427,40 @@ class AppInformationService extends BaseService {
user_count: totalUserCount,
grouped_stats: {
open_count: completeOpenStats,
- user_count: completeUserStats
+ user_count: completeUserStats,
},
- referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null
+ referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null,
};
}
}
// Handle non-grouped stats
- if (global.clickhouseClient) {
+ if ( global.clickhouseClient ) {
const openCountQuery = timeRange
? `SELECT COUNT(_id) AS open_count FROM app_opens
WHERE app_uid = '${app_uid}'
- AND ts >= ${Math.floor(timeRange.start/1000)}
- AND ts < ${Math.floor(timeRange.end/1000)}`
+ AND ts >= ${Math.floor(timeRange.start / 1000)}
+ AND ts < ${Math.floor(timeRange.end / 1000)}`
: `SELECT COUNT(_id) AS open_count FROM app_opens
WHERE app_uid = '${app_uid}'`;
const userCountQuery = timeRange
? `SELECT COUNT(DISTINCT user_id) AS uniqueUsers FROM app_opens
WHERE app_uid = '${app_uid}'
- AND ts >= ${Math.floor(timeRange.start/1000)}
- AND ts < ${Math.floor(timeRange.end/1000)}`
+ AND ts >= ${Math.floor(timeRange.start / 1000)}
+ AND ts < ${Math.floor(timeRange.end / 1000)}`
: `SELECT COUNT(DISTINCT user_id) AS uniqueUsers FROM app_opens
WHERE app_uid = '${app_uid}'`;
const [openResult, userResult] = await Promise.all([
global.clickhouseClient.query({
query: openCountQuery,
- format: 'JSONEachRow'
+ format: 'JSONEachRow',
}),
global.clickhouseClient.query({
query: userCountQuery,
- format: 'JSONEachRow'
- })
+ format: 'JSONEachRow',
+ }),
]);
const openRows = await openResult.json();
@@ -474,16 +469,16 @@ class AppInformationService extends BaseService {
const results = {
open_count: parseInt(openRows[0].open_count),
user_count: parseInt(userRows[0].uniqueUsers),
- referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null
+ referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null,
};
// Cache the results if period is 'all'
- if (period === 'all') {
+ if ( period === 'all' ) {
const key_open_count = `apps:open_count:uid:${app_uid}`;
const key_user_count = `apps:user_count:uid:${app_uid}`;
await Promise.all([
kv.set(key_open_count, results.open_count),
- kv.set(key_user_count, results.user_count)
+ kv.set(key_user_count, results.user_count),
]);
}
@@ -494,7 +489,7 @@ class AppInformationService extends BaseService {
const baseUserQuery = 'SELECT COUNT(DISTINCT user_id) AS user_count FROM app_opens WHERE app_uid = ?';
const generateQuery = (baseQuery, timeRange) => {
- if (!timeRange) return baseQuery;
+ if ( ! timeRange ) return baseQuery;
return `${baseQuery} AND ts >= ? AND ts < ?`;
};
@@ -504,22 +499,22 @@ class AppInformationService extends BaseService {
const [openResult, userResult] = await Promise.all([
db.read(openQuery, queryParams),
- db.read(userQuery, queryParams)
+ db.read(userQuery, queryParams),
]);
const results = {
open_count: parseInt(openResult[0].open_count),
user_count: parseInt(userResult[0].user_count),
- referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null
+ referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null,
};
// Cache the results if period is 'all'
- if (period === 'all') {
+ if ( period === 'all' ) {
const key_open_count = `apps:open_count:uid:${app_uid}`;
const key_user_count = `apps:user_count:uid:${app_uid}`;
await Promise.all([
kv.set(key_open_count, results.open_count),
- kv.set(key_user_count, results.user_count)
+ kv.set(key_user_count, results.user_count),
]);
}
@@ -529,10 +524,10 @@ class AppInformationService extends BaseService {
/**
* Refreshes the application cache by querying the database for all apps and updating the key-value store.
- *
+ *
* @async
* @returns {Promise} A promise that resolves when the cache refresh operation is complete.
- *
+ *
* @notes
* - This method logs a tick event for performance monitoring.
* - It populates the cache with app data indexed by name, id, and uid.
@@ -544,13 +539,12 @@ class AppInformationService extends BaseService {
let apps = await db.read('SELECT * FROM apps');
for ( const app of apps ) {
- kv.set('apps:name:' + app.name, app);
- kv.set('apps:id:' + app.id, app);
- kv.set('apps:uid:' + app.uid, app);
+ kv.set(`apps:name:${ app.name}`, app);
+ kv.set(`apps:id:${ app.id}`, app);
+ kv.set(`apps:uid:${ app.uid}`, app);
}
}
-
/**
* Refreshes the cache of app statistics including open and user counts.
*
@@ -571,35 +565,30 @@ class AppInformationService extends BaseService {
// I'm not suggesting a specific solution for here, but it's
// interesting to think about.
- const apps = await db.read(`SELECT uid FROM apps`);
+ const apps = await db.read('SELECT uid FROM apps');
for ( const app of apps ) {
const key_open_count = `apps:open_count:uid:${app.uid}`;
- const { open_count } = (await db.read(
- `SELECT COUNT(_id) AS open_count FROM app_opens WHERE app_uid = ?`,
- [app.uid]
- ))[0];
+ const { open_count } = (await db.read('SELECT COUNT(_id) AS open_count FROM app_opens WHERE app_uid = ?',
+ [app.uid]))[0];
kv.set(key_open_count, open_count);
const key_user_count = `apps:user_count:uid:${app.uid}`;
- const { user_count } = (await db.read(
- `SELECT COUNT(DISTINCT user_id) AS user_count FROM app_opens WHERE app_uid = ?`,
- [app.uid]
- ))[0];
+ const { user_count } = (await db.read('SELECT COUNT(DISTINCT user_id) AS user_count FROM app_opens WHERE app_uid = ?',
+ [app.uid]))[0];
kv.set(key_user_count, user_count);
}
}
-
/**
* Refreshes the cache of app referral statistics.
- *
+ *
* This method queries the database for user counts referred by each app's origin URL
* and updates the cache with the referral counts for each app.
- *
+ *
* @notes
* - This method logs a tick event for performance monitoring.
- *
+ *
* @async
* @returns {Promise} A promise that resolves when the cache refresh operation is complete.
*/
@@ -608,7 +597,7 @@ class AppInformationService extends BaseService {
const db = this.services.get('database').get(DB_READ, 'apps');
- const apps = await db.read(`SELECT uid, index_url FROM apps`);
+ const apps = await db.read('SELECT uid, index_url FROM apps');
for ( const app of apps ) {
const origin = origin_from_url(app.index_url);
@@ -627,10 +616,8 @@ class AppInformationService extends BaseService {
}
const key_referral_count = `apps:referral_count:uid:${app.uid}`;
- const { referral_count } = (await db.read(
- `SELECT COUNT(id) AS referral_count FROM user WHERE referrer LIKE ?`,
- [origin + '%']
- ))[0];
+ const { referral_count } = (await db.read('SELECT COUNT(id) AS referral_count FROM user WHERE referrer LIKE ?',
+ [`${origin }%`]))[0];
kv.set(key_referral_count, referral_count);
}
@@ -638,19 +625,18 @@ class AppInformationService extends BaseService {
this.log.info('DONE refresh app stat referrals');
}
-
/**
* Updates the cache with recently updated apps.
- *
+ *
* @description This method refreshes the cache containing the most recently updated applications.
* It fetches all app UIDs, retrieves the corresponding app data, filters for approved apps,
* sorts them by timestamp in descending order, and updates the 'recent' collection with
* the UIDs of the top 50 most recent apps.
- *
+ *
* @returns {Promise} Resolves when the cache has been updated.
*/
async _refresh_recent_cache () {
- const app_keys = kv.keys(`apps:uid:*`);
+ const app_keys = kv.keys('apps:uid:*');
let apps = [];
for ( const key of app_keys ) {
@@ -666,21 +652,20 @@ class AppInformationService extends BaseService {
this.collections.recent = apps.map(app => app.uid).slice(0, 50);
}
-
/**
* Refreshes the cache of tags associated with apps.
- *
+ *
* This method iterates through all approved apps, extracts their tags,
* and organizes them into a structured format for quick lookups.
- *
+ *
* This data is used by the `/query/app` router to facilitate tag-based
* app discovery and categorization.
- *
+ *
* @async
* @returns {Promise}
*/
async _refresh_tags () {
- const app_keys = kv.keys(`apps:uid:*`);
+ const app_keys = kv.keys('apps:uid:*');
let apps = [];
for ( const key of app_keys ) {
@@ -713,17 +698,16 @@ class AppInformationService extends BaseService {
this.tags = new_tags;
}
-
/**
* Deletes an application from the system.
- *
+ *
* This method performs the following actions:
* - Retrieves the app data from cache or database if not provided.
* - Deletes the app record from the database.
* - Removes the app from all relevant caches (by name, id, and uid).
* - Removes the app from the recent collection if present.
* - Removes the app from any associated tags.
- *
+ *
* @param {string} app_uid - The unique identifier of the app to be deleted.
* @param {Object} [app] - The app object, if already fetched. If not provided, it will be retrieved.
* @throws {Error} If the app is not found in either cache or database.
@@ -732,27 +716,23 @@ class AppInformationService extends BaseService {
async delete_app (app_uid, app) {
const db = this.services.get('database').get(DB_READ, 'apps');
- app = app ?? kv.get('apps:uid:' + app_uid);
+ app = app ?? kv.get(`apps:uid:${ app_uid}`);
if ( ! app ) {
- app = (await db.read(
- `SELECT * FROM apps WHERE uid = ?`,
- [app_uid]
- ))[0];
+ app = (await db.read('SELECT * FROM apps WHERE uid = ?',
+ [app_uid]))[0];
}
if ( ! app ) {
throw new Error('app not found');
}
- await db.write(
- `DELETE FROM apps WHERE uid = ? LIMIT 1`,
- [app_uid]
- );
+ await db.write('DELETE FROM apps WHERE uid = ? LIMIT 1',
+ [app_uid]);
// remove from caches
- kv.del('apps:name:' + app.name);
- kv.del('apps:id:' + app.id);
- kv.del('apps:uid:' + app.uid);
+ kv.del(`apps:name:${ app.name}`);
+ kv.del(`apps:id:${ app.id}`);
+ kv.del(`apps:uid:${ app.uid}`);
// remove from recent
const index = this.collections.recent.indexOf(app_uid);
@@ -775,42 +755,42 @@ class AppInformationService extends BaseService {
}
// Helper function to generate array of all periods between start and end dates
- generateAllPeriods(startDate, endDate, grouping) {
+ generateAllPeriods (startDate, endDate, grouping) {
const periods = [];
let currentDate = new Date(startDate);
-
+
// ???: In local debugging, `currentDate` evaluates to `Invalid Date`.
// Does this work in prod?
-
- while (currentDate <= endDate) {
+
+ while ( currentDate <= endDate ) {
let period;
- switch(grouping) {
- case 'hour':
- period = currentDate.toISOString().slice(0, 13) + ':00:00';
- currentDate.setHours(currentDate.getHours() + 1);
- break;
- case 'day':
- period = currentDate.toISOString().slice(0, 10);
- currentDate.setDate(currentDate.getDate() + 1);
- break;
- case 'week':
- // Get the ISO week number
- // TODO: Fix this use of `getWeekNumber`, which doesn't exist.
- // I was not able to invoke this branch due to other
- // blockers when testing locally so I'm leaving this as-is
- // because I can't test for regressions.
- const weekNum = String(getWeekNumber(currentDate)).padStart(2, '0');
- period = `${currentDate.getFullYear()}-${weekNum}`;
- currentDate.setDate(currentDate.getDate() + 7);
- break;
- case 'month':
- period = currentDate.toISOString().slice(0, 7);
- currentDate.setMonth(currentDate.getMonth() + 1);
- break;
- case 'year':
- period = currentDate.getFullYear().toString();
- currentDate.setFullYear(currentDate.getFullYear() + 1);
- break;
+ switch ( grouping ) {
+ case 'hour':
+ period = `${currentDate.toISOString().slice(0, 13) }:00:00`;
+ currentDate.setHours(currentDate.getHours() + 1);
+ break;
+ case 'day':
+ period = currentDate.toISOString().slice(0, 10);
+ currentDate.setDate(currentDate.getDate() + 1);
+ break;
+ case 'week':
+ // Get the ISO week number
+ // TODO: Fix this use of `getWeekNumber`, which doesn't exist.
+ // I was not able to invoke this branch due to other
+ // blockers when testing locally so I'm leaving this as-is
+ // because I can't test for regressions.
+ const weekNum = String(getWeekNumber(currentDate)).padStart(2, '0');
+ period = `${currentDate.getFullYear()}-${weekNum}`;
+ currentDate.setDate(currentDate.getDate() + 7);
+ break;
+ case 'month':
+ period = currentDate.toISOString().slice(0, 7);
+ currentDate.setMonth(currentDate.getMonth() + 1);
+ break;
+ case 'year':
+ period = currentDate.getFullYear().toString();
+ currentDate.setFullYear(currentDate.getFullYear() + 1);
+ break;
}
periods.push({ period, count: 0 });
}
@@ -818,43 +798,43 @@ class AppInformationService extends BaseService {
}
// Helper function to get ISO week number
- getWeekNumber(date) {
+ getWeekNumber (date) {
const target = new Date(date.valueOf());
const dayNumber = (date.getDay() + 6) % 7;
target.setDate(target.getDate() - dayNumber + 3);
const firstThursday = target.valueOf();
target.setMonth(0, 1);
- if (target.getDay() !== 4) {
+ if ( target.getDay() !== 4 ) {
target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7);
}
return 1 + Math.ceil((firstThursday - target) / 604800000);
}
// Helper function to merge actual data with generated periods
- mergeWithGeneratedPeriods(actualData, allPeriods, stats_grouping) {
+ mergeWithGeneratedPeriods (actualData, allPeriods, stats_grouping) {
// Create a map of period to count from actual data
// First normalize the period format from both MySQL and ClickHouse
const dataMap = new Map(actualData.map(item => {
let period = item.period;
// For ClickHouse results, convert the timestamp to match the expected format
- if (item.period instanceof Date) {
- switch(stats_grouping) {
- case 'hour':
- period = item.period.toISOString().slice(0, 13) + ':00:00';
- break;
- case 'day':
- period = item.period.toISOString().slice(0, 10);
- break;
- case 'week':
- const weekNum = String(this.getWeekNumber(item.period)).padStart(2, '0');
- period = `${item.period.getFullYear()}-${weekNum}`;
- break;
- case 'month':
- period = item.period.toISOString().slice(0, 7);
- break;
- case 'year':
- period = item.period.getFullYear().toString();
- break;
+ if ( item.period instanceof Date ) {
+ switch ( stats_grouping ) {
+ case 'hour':
+ period = `${item.period.toISOString().slice(0, 13) }:00:00`;
+ break;
+ case 'day':
+ period = item.period.toISOString().slice(0, 10);
+ break;
+ case 'week':
+ const weekNum = String(this.getWeekNumber(item.period)).padStart(2, '0');
+ period = `${item.period.getFullYear()}-${weekNum}`;
+ break;
+ case 'month':
+ period = item.period.toISOString().slice(0, 7);
+ break;
+ case 'year':
+ period = item.period.getFullYear().toString();
+ break;
}
}
return [period, parseInt(item.count)];
@@ -865,7 +845,7 @@ class AppInformationService extends BaseService {
const count = dataMap.get(periodObj.period);
return {
period: periodObj.period,
- count: count !== undefined ? count : 0
+ count: count !== undefined ? count : 0,
};
});
}
diff --git a/src/backend/src/modules/apps/AppsModule.js b/src/backend/src/modules/apps/AppsModule.js
index 5eb0e5d392..5cb742612b 100644
--- a/src/backend/src/modules/apps/AppsModule.js
+++ b/src/backend/src/modules/apps/AppsModule.js
@@ -1,23 +1,23 @@
/*
* 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 .
*/
-const { AdvancedBase } = require("@heyputer/putility");
+const { AdvancedBase } = require('@heyputer/putility');
class AppsModule extends AdvancedBase {
async install (context) {
@@ -41,5 +41,5 @@ class AppsModule extends AdvancedBase {
}
module.exports = {
- AppsModule
+ AppsModule,
};
diff --git a/src/backend/src/modules/apps/OldAppNameService.js b/src/backend/src/modules/apps/OldAppNameService.js
index 5af9ec7152..ca52acab64 100644
--- a/src/backend/src/modules/apps/OldAppNameService.js
+++ b/src/backend/src/modules/apps/OldAppNameService.js
@@ -1,24 +1,24 @@
/*
* 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 .
*/
-const BaseService = require("../../services/BaseService");
-const { DB_READ } = require("../../services/database/consts");
+const BaseService = require('../../services/BaseService');
+const { DB_READ } = require('../../services/database/consts');
const N_MONTHS = 4;
@@ -33,32 +33,27 @@ class OldAppNameService extends BaseService {
const svc_event = this.services.get('event');
svc_event.on('app.rename', async (_, { app_uid, old_name }) => {
this.log.info('GOT EVENT', { app_uid, old_name });
- await this.db.write(
- 'INSERT INTO `old_app_names` (`app_uid`, `name`) VALUES (?, ?)',
- [app_uid, old_name]
- );
+ await this.db.write('INSERT INTO `old_app_names` (`app_uid`, `name`) VALUES (?, ?)',
+ [app_uid, old_name]);
});
}
async check_app_name (name) {
- const rows = await this.db.read(
- 'SELECT * FROM `old_app_names` WHERE `name` = ?',
- [name]
- );
+ const rows = await this.db.read('SELECT * FROM `old_app_names` WHERE `name` = ?',
+ [name]);
if ( rows.length === 0 ) return;
// Check if the app has been renamed in the last N months
const [row] = rows;
const timestamp = row.timestamp instanceof Date ? row.timestamp : new Date(
- // Ensure timestamp ir processed as UTC
- row.timestamp.endsWith('Z') ? row.timestamp : row.timestamp + 'Z'
- );
+ // Ensure timestamp ir processed as UTC
+ row.timestamp.endsWith('Z') ? row.timestamp : `${row.timestamp }Z`);
const age = Date.now() - timestamp.getTime();
// const n_ms = 60 * 1000;
- const n_ms = N_MONTHS * 30 * 24 * 60 * 60 * 1000
+ const n_ms = N_MONTHS * 30 * 24 * 60 * 60 * 1000;
this.log.info('AGE INFO', {
input_time: row.timestamp,
age,
@@ -66,10 +61,8 @@ class OldAppNameService extends BaseService {
});
if ( age > n_ms ) {
// Remove record
- await this.db.write(
- 'DELETE FROM `old_app_names` WHERE `id` = ?',
- [row.id]
- );
+ await this.db.write('DELETE FROM `old_app_names` WHERE `id` = ?',
+ [row.id]);
// Return undefined
return;
}
@@ -81,10 +74,8 @@ class OldAppNameService extends BaseService {
}
async remove_name (id) {
- await this.db.write(
- 'DELETE FROM `old_app_names` WHERE `id` = ?',
- [id]
- );
+ await this.db.write('DELETE FROM `old_app_names` WHERE `id` = ?',
+ [id]);
}
}
diff --git a/src/backend/src/modules/apps/ProtectedAppService.js b/src/backend/src/modules/apps/ProtectedAppService.js
index 4a08991fa7..ccd6a1884f 100644
--- a/src/backend/src/modules/apps/ProtectedAppService.js
+++ b/src/backend/src/modules/apps/ProtectedAppService.js
@@ -17,12 +17,11 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const { get_app } = require("../../helpers");
-const { UserActorType } = require("../../services/auth/Actor");
+const { get_app } = require('../../helpers');
+const { UserActorType } = require('../../services/auth/Actor');
const { PermissionImplicator, PermissionUtil, PermissionRewriter } =
- require("../../services/auth/permissionUtils.mjs");
-const BaseService = require("../../services/BaseService");
-
+ require('../../services/auth/permissionUtils.mjs');
+const BaseService = require('../../services/BaseService');
/**
* @class ProtectedAppService
@@ -53,9 +52,7 @@ class ProtectedAppService extends BaseService {
rewriter: async permission => {
const [_1, name, ...rest] = PermissionUtil.split(permission);
const app = await get_app({ name });
- return PermissionUtil.join(
- _1, `uid#${app.uid}`, ...rest,
- );
+ return PermissionUtil.join(_1, `uid#${app.uid}`, ...rest);
},
}));
@@ -66,25 +63,25 @@ class ProtectedAppService extends BaseService {
return permission.startsWith('app:');
},
checker: async ({ actor, permission }) => {
- if ( !(actor.type instanceof UserActorType) ) {
+ if ( ! (actor.type instanceof UserActorType) ) {
return undefined;
}
-
+
const parts = PermissionUtil.split(permission);
if ( parts.length !== 3 ) return undefined;
-
+
const [_, uid_part, lvl] = parts;
if ( lvl !== 'access' ) return undefined;
-
+
// track: slice a prefix
const uid = uid_part.slice('uid#'.length);
-
+
const app = await get_app({ uid });
if ( app.owner_user_id !== actor.type.user.id ) {
return undefined;
}
-
+
return {};
},
}));
diff --git a/src/backend/src/modules/apps/RecommendedAppsService.js b/src/backend/src/modules/apps/RecommendedAppsService.js
index 6d08d7f4d3..3e072ea0c1 100644
--- a/src/backend/src/modules/apps/RecommendedAppsService.js
+++ b/src/backend/src/modules/apps/RecommendedAppsService.js
@@ -1,24 +1,24 @@
/*
* 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 .
*/
-const { get_app } = require("../../helpers");
-const BaseService = require("../../services/BaseService");
+const { get_app } = require('../../helpers');
+const BaseService = require('../../services/BaseService');
const get_apps = async ({ specifiers }) => {
return await Promise.all(specifiers.map(async (specifier) => {
@@ -73,13 +73,13 @@ class RecommendedAppsService extends BaseService {
_construct () {
this.app_names = new Set(RecommendedAppsService.APP_NAMES);
}
-
+
['__on_boot.consolidation'] () {
const svc_appIcon = this.services.get('app-icon');
const svc_event = this.services.get('event');
svc_event.on('apps.invalidate', (_, { app }) => {
const sizes = svc_appIcon.get_sizes();
-
+
this.log.noticeme('Invalidating recommended apps', { app, sizes });
// If it's a single-app invalidation, only invalidate if the
@@ -98,9 +98,8 @@ class RecommendedAppsService extends BaseService {
}
async get_recommended_apps ({ icon_size }) {
- const recommended_cache_key = 'global:recommended-apps' + (
- icon_size ? `:icon-size:${icon_size}` : ''
- );
+ const recommended_cache_key = `global:recommended-apps${
+ icon_size ? `:icon-size:${icon_size}` : ''}`;
let recommended = kv.get(recommended_cache_key);
if ( recommended ) return recommended;
@@ -108,8 +107,8 @@ class RecommendedAppsService extends BaseService {
// Prepare each app for returning to user by only returning the necessary fields
// and adding them to the retobj array
recommended = (await get_apps({
- specifiers: Array.from(this.app_names).map(name => ({ name }))
- })).filter(app => !! app).map(app => {
+ specifiers: Array.from(this.app_names).map(name => ({ name })),
+ })).filter(app => !!app).map(app => {
return {
uuid: app.uid,
name: app.name,
@@ -120,7 +119,7 @@ class RecommendedAppsService extends BaseService {
index_url: app.index_url,
};
});
-
+
const svc_appIcon = this.services.get('app-icon');
// Iconify apps
@@ -132,7 +131,7 @@ class RecommendedAppsService extends BaseService {
}
kv.set(recommended_cache_key, recommended);
-
+
return recommended;
}
}
diff --git a/src/backend/src/modules/apps/default-app-icon.js b/src/backend/src/modules/apps/default-app-icon.js
index 2c938fca8a..43ede5d5eb 100644
--- a/src/backend/src/modules/apps/default-app-icon.js
+++ b/src/backend/src/modules/apps/default-app-icon.js
@@ -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 .
*/
diff --git a/src/backend/src/modules/broadcast/BroadcastModule.js b/src/backend/src/modules/broadcast/BroadcastModule.js
index 96965534f8..ec2367aa21 100644
--- a/src/backend/src/modules/broadcast/BroadcastModule.js
+++ b/src/backend/src/modules/broadcast/BroadcastModule.js
@@ -1,23 +1,23 @@
/*
* 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 .
*/
-const { AdvancedBase } = require("@heyputer/putility");
+const { AdvancedBase } = require('@heyputer/putility');
class BroadcastModule extends AdvancedBase {
async install (context) {
diff --git a/src/backend/src/modules/broadcast/BroadcastService.js b/src/backend/src/modules/broadcast/BroadcastService.js
index bd9bfdabe5..0cbdadafa7 100644
--- a/src/backend/src/modules/broadcast/BroadcastService.js
+++ b/src/backend/src/modules/broadcast/BroadcastService.js
@@ -16,23 +16,23 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const BaseService = require("../../services/BaseService");
-const { CLink } = require("./connection/CLink");
-const { SLink } = require("./connection/SLink");
-const { Context } = require("../../util/context");
+const BaseService = require('../../services/BaseService');
+const { CLink } = require('./connection/CLink');
+const { SLink } = require('./connection/SLink');
+const { Context } = require('../../util/context');
class BroadcastService extends BaseService {
static MODULES = {
express: require('express'),
// ['socket.io']: require('socket.io'),
};
-
+
_construct () {
this.peers_ = [];
this.connections_ = [];
this.trustedPublicKeys_ = {};
}
-
+
async _init () {
const peers = this.config.peers ?? [];
for ( const peer_config of peers ) {
@@ -45,16 +45,16 @@ class BroadcastService extends BaseService {
this.peers_.push(peer);
peer.connect();
}
-
+
this._register_commands(this.services.get('commands'));
-
+
const svc_event = this.services.get('event');
svc_event.on('outer.*', this.on_event.bind(this));
}
-
+
async on_event (key, data, meta) {
if ( meta.from_outside ) return;
-
+
for ( const peer of this.peers_ ) {
try {
peer.send({ key, data, meta });
@@ -63,18 +63,18 @@ class BroadcastService extends BaseService {
}
}
}
-
+
async ['__on_install.websockets'] () {
const svc_event = this.services.get('event');
const svc_webServer = this.services.get('web-server');
-
+
const server = svc_webServer.get_server();
const io = require('socket.io')(server, {
cors: { origin: '*' },
path: '/wssinternal',
});
-
+
io.on('connection', async socket => {
const conn = new SLink({
keys: this.config.keys,
@@ -82,17 +82,16 @@ class BroadcastService extends BaseService {
socket,
});
this.connections_.push(conn);
-
+
conn.channels.message.on(({ key, data, meta }) => {
if ( meta.from_outside ) {
this.log.noticeme('possible over-sending');
return;
}
-
+
if ( key === 'test' ) {
- this.log.noticeme(`test message: ` +
- JSON.stringify(data)
- );
+ this.log.noticeme(`test message: ${
+ JSON.stringify(data)}`);
}
meta.from_outside = true;
@@ -103,7 +102,7 @@ class BroadcastService extends BaseService {
});
});
}
-
+
_register_commands (commands) {
commands.registerCommands('broadcast', [
{
@@ -112,10 +111,10 @@ class BroadcastService extends BaseService {
handler: async (args, ctx) => {
this.on_event('test', {
contents: 'I am a test message',
- }, {})
- }
- }
- ])
+ }, {});
+ },
+ },
+ ]);
}
}
diff --git a/src/backend/src/modules/broadcast/connection/BaseLink.js b/src/backend/src/modules/broadcast/connection/BaseLink.js
index a42db0c3e2..63eada97e1 100644
--- a/src/backend/src/modules/broadcast/connection/BaseLink.js
+++ b/src/backend/src/modules/broadcast/connection/BaseLink.js
@@ -1,24 +1,24 @@
/*
* 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 .
*/
-const { AdvancedBase } = require("@heyputer/putility");
-const { ChannelFeature } = require("../../../traits/ChannelFeature");
+const { AdvancedBase } = require('@heyputer/putility');
+const { ChannelFeature } = require('../../../traits/ChannelFeature');
class BaseLink extends AdvancedBase {
static FEATURES = [
diff --git a/src/backend/src/modules/broadcast/connection/CLink.js b/src/backend/src/modules/broadcast/connection/CLink.js
index 0788f90ae6..28233610eb 100644
--- a/src/backend/src/modules/broadcast/connection/CLink.js
+++ b/src/backend/src/modules/broadcast/connection/CLink.js
@@ -1,24 +1,24 @@
/*
* 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 .
*/
-const { BaseLink } = require("./BaseLink");
-const { KeyPairHelper } = require("./KeyPairHelper");
+const { BaseLink } = require('./BaseLink');
+const { KeyPairHelper } = require('./KeyPairHelper');
/**
* Client-side link that establishes an encrypted socket.io connection.
@@ -34,7 +34,7 @@ class CLink extends BaseLink {
* Encrypts the data using AES-256-CBC and sends it through the socket.
* The data is JSON stringified, encrypted with a random IV, and transmitted
* as a buffer along with the IV.
- *
+ *
* @param {*} data - The data to be encrypted and sent through the socket
* @returns {void}
*/
@@ -43,11 +43,9 @@ class CLink extends BaseLink {
const require = this.require;
const crypto = require('crypto');
const iv = crypto.randomBytes(16);
- const cipher = crypto.createCipheriv(
- 'aes-256-cbc',
- this.aesKey,
- iv,
- );
+ const cipher = crypto.createCipheriv('aes-256-cbc',
+ this.aesKey,
+ iv);
const jsonified = JSON.stringify(data);
let buffers = [];
buffers.push(cipher.update(Buffer.from(jsonified, 'utf-8')));
@@ -96,11 +94,11 @@ class CLink extends BaseLink {
extraHeaders: {
...(this.config.host ? {
Host: this.config.host,
- } : {})
- }
+ } : {}),
+ },
});
socket.on('connect', () => {
- this.log.info(`connected`, {
+ this.log.info('connected', {
address,
});
@@ -115,19 +113,17 @@ class CLink extends BaseLink {
socket.send({
$: 'take-my-key',
key: this.keys.public,
- message: kp_helper.write(
- this.aesKey.toString('base64')
- ),
+ message: kp_helper.write(this.aesKey.toString('base64')),
});
this.state = this.constructor.ONLINE;
});
socket.on('disconnect', () => {
- this.log.info(`disconnected`, {
+ this.log.info('disconnected', {
address,
});
});
socket.on('connect_error', e => {
- this.log.info(`connection error`, {
+ this.log.info('connection error', {
address,
message: e.message,
});
diff --git a/src/backend/src/modules/broadcast/connection/KeyPairHelper.js b/src/backend/src/modules/broadcast/connection/KeyPairHelper.js
index 2360b079b6..98b0cae1b8 100644
--- a/src/backend/src/modules/broadcast/connection/KeyPairHelper.js
+++ b/src/backend/src/modules/broadcast/connection/KeyPairHelper.js
@@ -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 .
*/
@@ -23,7 +23,7 @@ class KeyPairHelper extends AdvancedBase {
static MODULES = {
tweetnacl: require('tweetnacl'),
};
-
+
constructor ({
kpublic,
ksecret,
@@ -33,17 +33,17 @@ class KeyPairHelper extends AdvancedBase {
this.ksecret = ksecret;
this.nonce_ = 0;
}
-
+
to_nacl_key_ (key) {
console.log('WUT', key);
const full_buffer = Buffer.from(key, 'base64');
// Remove version byte (assumed to be 0x31 and ignored for now)
const buffer = full_buffer.slice(1);
-
+
return new Uint8Array(buffer);
}
-
+
get naclSecret () {
return this.naclSecret_ ?? (
this.naclSecret_ = this.to_nacl_key_(this.ksecret));
@@ -52,36 +52,31 @@ class KeyPairHelper extends AdvancedBase {
return this.naclPublic_ ?? (
this.naclPublic_ = this.to_nacl_key_(this.kpublic));
}
-
+
write (text) {
const require = this.require;
const nacl = require('tweetnacl');
const nonce = nacl.randomBytes(nacl.box.nonceLength);
const message = {};
-
+
const textUint8 = new Uint8Array(Buffer.from(text, 'utf-8'));
- const encryptedText = nacl.box(
- textUint8, nonce,
- this.naclPublic, this.naclSecret
- );
+ const encryptedText = nacl.box(textUint8, nonce, this.naclPublic, this.naclSecret);
message.text = Buffer.from(encryptedText);
message.nonce = Buffer.from(nonce);
-
+
return message;
}
-
+
read (message) {
const require = this.require;
const nacl = require('tweetnacl');
-
- const arr = nacl.box.open(
- new Uint8Array(message.text),
- new Uint8Array(message.nonce),
- this.naclPublic,
- this.naclSecret,
- );
-
+
+ const arr = nacl.box.open(new Uint8Array(message.text),
+ new Uint8Array(message.nonce),
+ this.naclPublic,
+ this.naclSecret);
+
return Buffer.from(arr).toString('utf-8');
}
}
diff --git a/src/backend/src/modules/broadcast/connection/SLink.js b/src/backend/src/modules/broadcast/connection/SLink.js
index a915ed690f..dd8242da7f 100644
--- a/src/backend/src/modules/broadcast/connection/SLink.js
+++ b/src/backend/src/modules/broadcast/connection/SLink.js
@@ -1,24 +1,24 @@
/*
* 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 .
*/
-const { BaseLink } = require("./BaseLink");
-const { KeyPairHelper } = require("./KeyPairHelper");
+const { BaseLink } = require('./BaseLink');
+const { KeyPairHelper } = require('./KeyPairHelper');
class SLink extends BaseLink {
static AUTHENTICATING = {
@@ -29,14 +29,14 @@ class SLink extends BaseLink {
}
const trustedKeys = this.trustedKeys;
-
+
const hasKey = trustedKeys[data.key];
if ( ! hasKey ) {
this.disconnect();
return;
}
-
- const is_trusted = trustedKeys.hasOwnProperty(data.key)
+
+ const is_trusted = trustedKeys.hasOwnProperty(data.key);
if ( ! is_trusted ) {
this.disconnect();
return;
@@ -46,38 +46,36 @@ class SLink extends BaseLink {
kpublic: data.key,
ksecret: this.keys.secret,
});
-
+
const message = kp_helper.read(data.message);
this.aesKey = Buffer.from(message, 'base64');
-
+
this.state = this.constructor.ONLINE;
- }
+ },
};
static ONLINE = {
on_message (data) {
const require = this.require;
const crypto = require('crypto');
- const decipher = crypto.createDecipheriv(
- 'aes-256-cbc',
- this.aesKey,
- data.iv,
- )
+ const decipher = crypto.createDecipheriv('aes-256-cbc',
+ this.aesKey,
+ data.iv);
const buffers = [];
buffers.push(decipher.update(data.message));
buffers.push(decipher.final());
-
+
const rawjson = Buffer.concat(buffers).toString('utf-8');
-
+
const output = JSON.parse(rawjson);
-
+
this.channels.message.emit(output);
- }
- }
+ },
+ };
static OFFLINE = {
on_message () {
throw new Error('unexpected message');
- }
- }
+ },
+ };
_send () {
// TODO: implement as a fallback
diff --git a/src/backend/src/modules/captcha/CaptchaModule.js b/src/backend/src/modules/captcha/CaptchaModule.js
index d91a64dca6..5acdcdbce7 100644
--- a/src/backend/src/modules/captcha/CaptchaModule.js
+++ b/src/backend/src/modules/captcha/CaptchaModule.js
@@ -18,7 +18,7 @@
* along with this program. If not, see .
*/
-const { AdvancedBase } = require("@heyputer/putility");
+const { AdvancedBase } = require('@heyputer/putility');
const CaptchaService = require('./services/CaptchaService');
/**
@@ -30,14 +30,14 @@ const CaptchaService = require('./services/CaptchaService');
* that can be used to protect routes and determine captcha requirements.
*/
class CaptchaModule extends AdvancedBase {
- async install(context) {
-
+ async install (context) {
+
// Get services from context
const services = context.get('services');
-
+
// Register the captcha service
services.registerService('captcha', CaptchaService);
}
}
-module.exports = { CaptchaModule };
\ No newline at end of file
+module.exports = { CaptchaModule };
\ No newline at end of file
diff --git a/src/backend/src/modules/captcha/middleware/captcha-middleware.js b/src/backend/src/modules/captcha/middleware/captcha-middleware.js
index 2712383e7e..c75a83ec8b 100644
--- a/src/backend/src/modules/captcha/middleware/captcha-middleware.js
+++ b/src/backend/src/modules/captcha/middleware/captcha-middleware.js
@@ -17,14 +17,14 @@
* along with this program. If not, see .
*/
-const APIError = require("../../../api/APIError");
-const { Context } = require("../../../util/context");
+const APIError = require('../../../api/APIError');
+const { Context } = require('../../../util/context');
/**
* Middleware that checks if captcha verification is required
* This is the "first half" of the captcha verification process
* It determines if verification is needed but doesn't perform verification
- *
+ *
* @param {Object} options - Configuration options
* @param {boolean} [options.strictMode=true] - If true, fails closed on errors (more secure)
* @returns {Function} Express middleware function
@@ -32,7 +32,7 @@ const { Context } = require("../../../util/context");
const checkCaptcha = ({ svc_captcha }) => async (req, res, next) => {
// Get services from the Context
const services = Context.get('services');
-
+
if ( ! svc_captcha.enabled ) {
req.captchaRequired = false;
return next();
@@ -47,7 +47,7 @@ const checkCaptcha = ({ svc_captcha }) => async (req, res, next) => {
required: true,
};
await svc_event.emit('captcha.check', event);
-
+
// Set captcha requirement based on service status
req.captchaRequired = event.required;
next();
@@ -57,7 +57,7 @@ const checkCaptcha = ({ svc_captcha }) => async (req, res, next) => {
* Middleware that requires captcha verification
* This is the "second half" of the captcha verification process
* It uses the result from checkCaptcha to determine if verification is needed
- *
+ *
* @param {Object} options - Configuration options
* @param {boolean} [options.strictMode=true] - If true, fails closed on errors (more secure)
* @returns {Function} Express middleware function
@@ -66,72 +66,72 @@ const requireCaptcha = (options = {}) => async (req, res, next) => {
if ( ! req.captchaRequired ) {
return next();
}
-
+
const services = Context.get('services');
-
+
try {
let captchaService;
try {
captchaService = services.get('captcha');
- } catch (error) {
+ } catch ( error ) {
console.warn('Captcha verification: required service not available', error);
return next(APIError.create('internal_error', null, {
message: 'Captcha service unavailable',
- status: 503
+ status: 503,
}));
}
// Fail closed if captcha service doesn't exist or isn't properly initialized
- if (!captchaService || typeof captchaService.verifyCaptcha !== 'function') {
+ if ( !captchaService || typeof captchaService.verifyCaptcha !== 'function' ) {
return next(APIError.create('internal_error', null, {
message: 'Captcha service misconfigured',
- status: 500
+ status: 500,
}));
}
-
+
// Check for captcha token and answer in request
const captchaToken = req.body.captchaToken;
const captchaAnswer = req.body.captchaAnswer;
- if (!captchaToken || !captchaAnswer) {
+ if ( !captchaToken || !captchaAnswer ) {
return next(APIError.create('captcha_required', null, {
message: 'Captcha verification required',
- status: 400
+ status: 400,
}));
}
-
+
// Verify the captcha
let isValid;
try {
isValid = captchaService.verifyCaptcha(captchaToken, captchaAnswer);
- } catch (verifyError) {
+ } catch ( verifyError ) {
console.error('Captcha verification: threw an error', verifyError);
return next(APIError.create('captcha_invalid', null, {
message: 'Captcha verification failed',
- status: 400
+ status: 400,
}));
}
-
+
// Check verification result
- if (!isValid) {
+ if ( ! isValid ) {
return next(APIError.create('captcha_invalid', null, {
message: 'Invalid captcha response',
- status: 400
+ status: 400,
}));
}
// Captcha verified successfully, continue
next();
- } catch (error) {
+ } catch ( error ) {
console.error('Captcha verification: unexpected error', error);
return next(APIError.create('internal_error', null, {
message: 'Captcha verification failed',
- status: 500
+ status: 500,
}));
}
};
module.exports = {
checkCaptcha,
- requireCaptcha
-};
\ No newline at end of file
+ requireCaptcha,
+};
\ No newline at end of file
diff --git a/src/backend/src/modules/captcha/services/CaptchaService.js b/src/backend/src/modules/captcha/services/CaptchaService.js
index 3dac3cd1f3..1260dcda7d 100644
--- a/src/backend/src/modules/captcha/services/CaptchaService.js
+++ b/src/backend/src/modules/captcha/services/CaptchaService.js
@@ -33,30 +33,30 @@ class CaptchaService extends BaseService {
/**
* Initializes the captcha service with configuration and storage
*/
- async _construct() {
+ async _construct () {
// Load dependencies
this.crypto = require('crypto');
this.svgCaptcha = require('svg-captcha');
-
+
// In-memory token storage with expiration
this.captchaTokens = new Map();
-
+
// Service instance diagnostic tracking
this.serviceId = Math.random().toString(36).substring(2, 10);
this.requestCounter = 0;
-
+
// Get configuration from service config
this.enabled = this.config.enabled === true;
this.expirationTime = this.config.expirationTime || (10 * 60 * 1000); // 10 minutes default
this.difficulty = this.config.difficulty || 'medium';
this.testMode = this.config.testMode === true;
-
+
// Add a static test token for diagnostic purposes
this.captchaTokens.set('test-static-token', {
text: 'testanswer',
- expiresAt: Date.now() + (365 * 24 * 60 * 60 * 1000) // 1 year
+ expiresAt: Date.now() + (365 * 24 * 60 * 60 * 1000), // 1 year
});
-
+
// Flag to track if endpoints are registered
this.endpointsRegistered = false;
}
@@ -69,17 +69,17 @@ class CaptchaService extends BaseService {
/**
* Sets up API endpoints and cleanup tasks
*/
- async _init() {
- if (!this.enabled) {
+ async _init () {
+ if ( ! this.enabled ) {
this.log.debug('Captcha service is disabled');
return;
}
// Set up periodic cleanup
this.cleanupInterval = setInterval(() => this.cleanupExpiredTokens(), 15 * 60 * 1000);
-
+
// Register endpoints if not already done
- if (!this.endpointsRegistered) {
+ if ( ! this.endpointsRegistered ) {
this.registerEndpoints();
this.endpointsRegistered = true;
}
@@ -88,8 +88,8 @@ class CaptchaService extends BaseService {
/**
* Cleanup method called when service is being destroyed
*/
- async _destroy() {
- if (this.cleanupInterval) {
+ async _destroy () {
+ if ( this.cleanupInterval ) {
clearInterval(this.cleanupInterval);
}
this.captchaTokens.clear();
@@ -99,36 +99,36 @@ class CaptchaService extends BaseService {
* Registers the captcha API endpoints with the web service
* @private
*/
- registerEndpoints() {
- if (this.endpointsRegistered) {
+ registerEndpoints () {
+ if ( this.endpointsRegistered ) {
return;
}
-
+
try {
// Try to get the web service
let webService = null;
try {
webService = this.services.get('web-service');
- } catch (error) {
+ } catch ( error ) {
// Web service not available, try web-server
try {
webService = this.services.get('web-server');
- } catch (innerError) {
+ } catch ( innerError ) {
this.log.warn('Neither web-service nor web-server are available yet');
return;
}
}
-
- if (!webService || !webService.app) {
+
+ if ( !webService || !webService.app ) {
this.log.warn('Web service found but app is not available');
return;
}
const app = webService.app;
-
+
const api = this.require('express').Router();
app.use('/api/captcha', api);
-
+
// Generate captcha endpoint
Endpoint({
route: '/generate',
@@ -137,7 +137,7 @@ class CaptchaService extends BaseService {
const captcha = this.generateCaptcha();
res.json({
token: captcha.token,
- image: captcha.data
+ image: captcha.data,
});
},
}).attach(api);
@@ -148,41 +148,41 @@ class CaptchaService extends BaseService {
methods: ['POST'],
handler: (req, res) => {
const { token, answer } = req.body;
-
- if (!token || !answer) {
- return res.status(400).json({
- valid: false,
- error: 'Missing token or answer'
+
+ if ( !token || !answer ) {
+ return res.status(400).json({
+ valid: false,
+ error: 'Missing token or answer',
});
}
-
+
const isValid = this.verifyCaptcha(token, answer);
res.json({ valid: isValid });
},
}).attach(api);
-
+
// Special endpoint for automated testing
// This should be disabled in production
- if (this.testMode) {
+ if ( this.testMode ) {
app.post('/api/captcha/create-test-token', (req, res) => {
try {
const { token, answer } = req.body;
-
- if (!token || !answer) {
- return res.status(400).json({
- error: 'Missing token or answer'
+
+ if ( !token || !answer ) {
+ return res.status(400).json({
+ error: 'Missing token or answer',
});
}
-
+
// Store the test token with the provided answer
this.captchaTokens.set(token, {
text: answer.toLowerCase(),
- expiresAt: Date.now() + this.expirationTime
+ expiresAt: Date.now() + this.expirationTime,
});
-
+
this.log.debug(`Created test token: ${token} with answer: ${answer}`);
res.json({ success: true });
- } catch (error) {
+ } catch ( error ) {
this.log.error(`Error creating test token: ${error.message}`);
res.status(500).json({ error: 'Failed to create test token' });
}
@@ -204,15 +204,15 @@ class CaptchaService extends BaseService {
requestCounter: this.requestCounter,
hasStaticTestToken: this.captchaTokens.has('test-static-token'),
tokensState: Array.from(this.captchaTokens).map(([token, data]) => ({
- tokenPrefix: token.substring(0, 8) + '...',
+ tokenPrefix: `${token.substring(0, 8) }...`,
expiresAt: new Date(data.expiresAt).toISOString(),
expired: data.expiresAt < Date.now(),
- expectedAnswer: data.text
- }))
+ expectedAnswer: data.text,
+ })),
};
-
+
res.json(diagnosticInfo);
- } catch (error) {
+ } catch ( error ) {
this.log.error(`Error in diagnostic endpoint: ${error.message}`);
res.status(500).json({ error: 'Diagnostic error' });
}
@@ -224,16 +224,16 @@ class CaptchaService extends BaseService {
// Check if we're the same service instance
const currentTimestamp = Date.now();
const currentTokens = Array.from(this.captchaTokens.keys()).map(t => t.substring(0, 8));
-
+
// Create a test token that won't expire soon
- const debugToken = 'debug-' + this.crypto.randomBytes(8).toString('hex');
+ const debugToken = `debug-${ this.crypto.randomBytes(8).toString('hex')}`;
const debugAnswer = 'test123';
-
+
this.captchaTokens.set(debugToken, {
text: debugAnswer,
- expiresAt: currentTimestamp + (60 * 60 * 1000) // 1 hour
+ expiresAt: currentTimestamp + (60 * 60 * 1000), // 1 hour
});
-
+
// Information about the current service instance
const serviceInfo = {
message: 'Debug token created - use for testing captcha validation',
@@ -244,11 +244,11 @@ class CaptchaService extends BaseService {
tokensAfter: Array.from(this.captchaTokens.keys()).map(t => t.substring(0, 8)),
currentTokenCount: this.captchaTokens.size,
timestamp: currentTimestamp,
- processId: process.pid
+ processId: process.pid,
};
-
+
res.json(serviceInfo);
- } catch (error) {
+ } catch ( error ) {
this.log.error(`Error in debug-tokens endpoint: ${error.message}`);
res.status(500).json({ error: 'Debug token creation error' });
}
@@ -266,16 +266,16 @@ class CaptchaService extends BaseService {
enabled: this.enabled,
difficulty: this.difficulty,
expirationTime: this.expirationTime,
- testMode: this.testMode
+ testMode: this.testMode,
},
usingCentralizedConfig: true,
configConsistency: this.enabled === (this.enabled === true),
serviceId: this.serviceId,
- processId: process.pid
+ processId: process.pid,
};
-
+
res.json(configInfo);
- } catch (error) {
+ } catch ( error ) {
this.log.error(`Error in config-status endpoint: ${error.message}`);
res.status(500).json({ error: 'Configuration status error' });
}
@@ -286,30 +286,30 @@ class CaptchaService extends BaseService {
try {
// Create a test captcha
const testText = 'test123';
- const testToken = 'lifecycle-' + this.crypto.randomBytes(16).toString('hex');
-
+ const testToken = `lifecycle-${ this.crypto.randomBytes(16).toString('hex')}`;
+
// Store the test token
this.captchaTokens.set(testToken, {
text: testText,
- expiresAt: Date.now() + this.expirationTime
+ expiresAt: Date.now() + this.expirationTime,
});
-
+
// Verify the token exists
const tokenExists = this.captchaTokens.has(testToken);
// Try to verify with correct answer
const correctVerification = this.verifyCaptcha(testToken, testText);
// Check if token was deleted after verification
const tokenAfterVerification = this.captchaTokens.has(testToken);
-
+
// Create another test token
- const testToken2 = 'lifecycle2-' + this.crypto.randomBytes(16).toString('hex');
-
+ const testToken2 = `lifecycle2-${ this.crypto.randomBytes(16).toString('hex')}`;
+
// Store the test token
this.captchaTokens.set(testToken2, {
text: testText,
- expiresAt: Date.now() + this.expirationTime
+ expiresAt: Date.now() + this.expirationTime,
});
-
+
res.json({
message: 'Token lifecycle test completed',
serviceId: this.serviceId,
@@ -319,9 +319,9 @@ class CaptchaService extends BaseService {
verificationResult: correctVerification,
tokenRemovedAfterVerification: !tokenAfterVerification,
secondTokenCreated: this.captchaTokens.has(testToken2),
- processId: process.pid
+ processId: process.pid,
});
- } catch (error) {
+ } catch ( error ) {
console.error('TOKENS_TRACKING: Error in test-lifecycle endpoint:', error);
res.status(500).json({ error: 'Test lifecycle error' });
}
@@ -329,17 +329,17 @@ class CaptchaService extends BaseService {
this.endpointsRegistered = true;
this.log.debug('Captcha service endpoints registered successfully');
-
+
// Emit an event that captcha service is ready
try {
const eventService = this.services.get('event');
- if (eventService) {
+ if ( eventService ) {
eventService.emit('service-ready', 'captcha');
}
- } catch (error) {
+ } catch ( error ) {
// Ignore errors with event service
}
- } catch (error) {
+ } catch ( error ) {
this.log.warn(`Could not register captcha endpoints: ${error.message}`);
}
}
@@ -348,19 +348,19 @@ class CaptchaService extends BaseService {
* Generates a new captcha with a unique token
* @returns {Object} Object containing token and SVG image
*/
- generateCaptcha() {
+ generateCaptcha () {
console.log('====== CAPTCHA GENERATION DIAGNOSTIC ======');
console.log('TOKENS_TRACKING: generateCaptcha called. Service ID:', this.serviceId);
console.log('TOKENS_TRACKING: Token map size before generation:', this.captchaTokens.size);
console.log('TOKENS_TRACKING: Static test token exists:', this.captchaTokens.has('test-static-token'));
-
+
// Increment request counter for diagnostics
this.requestCounter++;
console.log('TOKENS_TRACKING: Request counter value:', this.requestCounter);
-
+
console.log('generateCaptcha called, service enabled:', this.enabled);
-
- if (!this.enabled) {
+
+ if ( ! this.enabled ) {
console.log('Generation SKIPPED: Captcha service is disabled');
throw new Error('Captcha service is disabled');
}
@@ -368,33 +368,33 @@ class CaptchaService extends BaseService {
// Configure captcha options based on difficulty
const options = this._getCaptchaOptions();
console.log('Using captcha options for difficulty:', this.difficulty);
-
+
// Generate the captcha
const captcha = this.svgCaptcha.create(options);
console.log('Captcha created with text:', captcha.text);
-
+
// Generate a unique token
const token = this.crypto.randomBytes(32).toString('hex');
- console.log('Generated token:', token.substring(0, 8) + '...');
-
+ console.log('Generated token:', `${token.substring(0, 8) }...`);
+
// Store token with captcha text and expiration
const expirationTime = Date.now() + this.expirationTime;
console.log('Token will expire at:', new Date(expirationTime));
-
+
console.log('TOKENS_TRACKING: Token map size before storing new token:', this.captchaTokens.size);
-
+
this.captchaTokens.set(token, {
text: captcha.text.toLowerCase(),
- expiresAt: expirationTime
+ expiresAt: expirationTime,
});
-
+
console.log('TOKENS_TRACKING: Token map size after storing new token:', this.captchaTokens.size);
console.log('Token stored in captchaTokens. Current token count:', this.captchaTokens.size);
this.log.debug(`Generated captcha with token: ${token}`);
-
+
return {
token: token,
- data: captcha.data
+ data: captcha.data,
};
}
@@ -404,22 +404,22 @@ class CaptchaService extends BaseService {
* @param {string} userAnswer - The user's answer to verify
* @returns {boolean} Whether the answer is valid
*/
- verifyCaptcha(token, userAnswer) {
+ verifyCaptcha (token, userAnswer) {
console.log('====== CAPTCHA SERVICE VERIFICATION DIAGNOSTIC ======');
console.log('TOKENS_TRACKING: verifyCaptcha called. Service ID:', this.serviceId);
console.log('TOKENS_TRACKING: Request counter during verification:', this.requestCounter);
console.log('TOKENS_TRACKING: Static test token exists:', this.captchaTokens.has('test-static-token'));
- console.log('TOKENS_TRACKING: Trying to verify token:', token ? token.substring(0, 8) + '...' : 'undefined');
-
- console.log('verifyCaptcha called with token:', token ? token.substring(0, 8) + '...' : 'undefined');
+ console.log('TOKENS_TRACKING: Trying to verify token:', token ? `${token.substring(0, 8) }...` : 'undefined');
+
+ console.log('verifyCaptcha called with token:', token ? `${token.substring(0, 8) }...` : 'undefined');
console.log('userAnswer:', userAnswer);
console.log('Service enabled:', this.enabled);
console.log('Number of tokens in captchaTokens:', this.captchaTokens.size);
-
+
// Service health check
this._checkServiceHealth();
-
- if (!this.enabled) {
+
+ if ( ! this.enabled ) {
console.log('Verification SKIPPED: Captcha service is disabled');
this.log.warn('Captcha verification attempted while service is disabled');
throw new Error('Captcha service is disabled');
@@ -428,43 +428,43 @@ class CaptchaService extends BaseService {
// Get captcha data for token
const captchaData = this.captchaTokens.get(token);
console.log('Captcha data found for token:', !!captchaData);
-
+
// Invalid token or expired
- if (!captchaData) {
+ if ( ! captchaData ) {
console.log('Verification FAILED: No data found for this token');
- console.log('TOKENS_TRACKING: Available tokens (first 8 chars):',
- Array.from(this.captchaTokens.keys()).map(t => t.substring(0, 8)));
+ console.log('TOKENS_TRACKING: Available tokens (first 8 chars):',
+ Array.from(this.captchaTokens.keys()).map(t => t.substring(0, 8)));
this.log.debug(`Invalid captcha token: ${token}`);
return false;
}
-
- if (captchaData.expiresAt < Date.now()) {
+
+ if ( captchaData.expiresAt < Date.now() ) {
console.log('Verification FAILED: Token expired at:', new Date(captchaData.expiresAt));
this.log.debug(`Expired captcha token: ${token}`);
return false;
}
-
+
// Normalize and compare answers
const normalizedUserAnswer = userAnswer.toLowerCase().trim();
console.log('Expected answer:', captchaData.text);
console.log('User answer (normalized):', normalizedUserAnswer);
const isValid = captchaData.text === normalizedUserAnswer;
console.log('Answer comparison result:', isValid);
-
+
// Remove token after verification (one-time use)
this.captchaTokens.delete(token);
console.log('Token removed after verification (one-time use)');
console.log('TOKENS_TRACKING: Token map size after removing used token:', this.captchaTokens.size);
-
+
this.log.debug(`Verified captcha token: ${token}, valid: ${isValid}`);
return isValid;
}
-
+
/**
* Simple diagnostic method to check service health
* @private
*/
- _checkServiceHealth() {
+ _checkServiceHealth () {
console.log('TOKENS_TRACKING: Service health check. ID:', this.serviceId, 'Token count:', this.captchaTokens.size);
return true;
}
@@ -472,34 +472,34 @@ class CaptchaService extends BaseService {
/**
* Removes expired captcha tokens from memory
*/
- cleanupExpiredTokens() {
+ cleanupExpiredTokens () {
console.log('TOKENS_TRACKING: Running token cleanup. Service ID:', this.serviceId);
console.log('TOKENS_TRACKING: Token map size before cleanup:', this.captchaTokens.size);
-
+
const now = Date.now();
let expiredCount = 0;
let validCount = 0;
-
+
// Log all tokens before cleanup
console.log('TOKENS_TRACKING: Current tokens before cleanup:');
- for (const [token, data] of this.captchaTokens.entries()) {
+ for ( const [token, data] of this.captchaTokens.entries() ) {
const isExpired = data.expiresAt < now;
console.log(`TOKENS_TRACKING: Token ${token.substring(0, 8)}... expires: ${new Date(data.expiresAt).toISOString()}, expired: ${isExpired}`);
-
- if (isExpired) {
+
+ if ( isExpired ) {
expiredCount++;
} else {
validCount++;
}
}
-
+
// Only do the actual cleanup if we found expired tokens
- if (expiredCount > 0) {
+ if ( expiredCount > 0 ) {
console.log(`TOKENS_TRACKING: Found ${expiredCount} expired tokens to remove and ${validCount} valid tokens to keep`);
-
+
// Clean up expired tokens
- for (const [token, data] of this.captchaTokens.entries()) {
- if (data.expiresAt < now) {
+ for ( const [token, data] of this.captchaTokens.entries() ) {
+ if ( data.expiresAt < now ) {
this.captchaTokens.delete(token);
console.log(`TOKENS_TRACKING: Deleted expired token: ${token.substring(0, 8)}...`);
}
@@ -507,24 +507,24 @@ class CaptchaService extends BaseService {
} else {
console.log('TOKENS_TRACKING: No expired tokens found, skipping cleanup');
}
-
+
// Skip cleanup for the static test token
- if (this.captchaTokens.has('test-static-token')) {
+ if ( this.captchaTokens.has('test-static-token') ) {
console.log('TOKENS_TRACKING: Static test token still exists after cleanup');
} else {
console.log('TOKENS_TRACKING: WARNING - Static test token was removed during cleanup');
-
+
// Restore the static test token for diagnostic purposes
this.captchaTokens.set('test-static-token', {
text: 'testanswer',
- expiresAt: Date.now() + (365 * 24 * 60 * 60 * 1000) // 1 year
+ expiresAt: Date.now() + (365 * 24 * 60 * 60 * 1000), // 1 year
});
console.log('TOKENS_TRACKING: Restored static test token');
}
-
+
console.log('TOKENS_TRACKING: Token map size after cleanup:', this.captchaTokens.size);
-
- if (expiredCount > 0) {
+
+ if ( expiredCount > 0 ) {
this.log.debug(`Cleaned up ${expiredCount} expired captcha tokens`);
}
}
@@ -534,39 +534,39 @@ class CaptchaService extends BaseService {
* @private
* @returns {Object} Captcha configuration options
*/
- _getCaptchaOptions() {
+ _getCaptchaOptions () {
const baseOptions = {
size: 6, // Default captcha length
ignoreChars: '0o1ilI', // Characters to avoid (confusing)
noise: 2, // Lines to add as noise
color: true,
- background: '#f0f0f0'
+ background: '#f0f0f0',
};
-
- switch (this.difficulty) {
- case 'easy':
- return {
- ...baseOptions,
- size: 4,
- width: 150,
- height: 50,
- noise: 1
- };
- case 'hard':
- return {
- ...baseOptions,
- size: 7,
- width: 200,
- height: 60,
- noise: 3
- };
- case 'medium':
- default:
- return {
- ...baseOptions,
- width: 180,
- height: 50
- };
+
+ switch ( this.difficulty ) {
+ case 'easy':
+ return {
+ ...baseOptions,
+ size: 4,
+ width: 150,
+ height: 50,
+ noise: 1,
+ };
+ case 'hard':
+ return {
+ ...baseOptions,
+ size: 7,
+ width: 200,
+ height: 60,
+ noise: 3,
+ };
+ case 'medium':
+ default:
+ return {
+ ...baseOptions,
+ width: 180,
+ height: 50,
+ };
}
}
@@ -575,47 +575,47 @@ class CaptchaService extends BaseService {
* This is used during initialization and can be called to check system status
* @returns {boolean} Whether the service is properly configured and functioning
*/
- verifySelfTest() {
+ verifySelfTest () {
try {
// Ensure required dependencies are available
- if (!this.svgCaptcha) {
+ if ( ! this.svgCaptcha ) {
this.log.error('Captcha service self-test failed: svg-captcha module not available');
return false;
}
-
- if (!this.enabled) {
+
+ if ( ! this.enabled ) {
this.log.warn('Captcha service self-test failed: service is disabled');
return false;
}
-
+
// Validate configuration
- if (!this.expirationTime || typeof this.expirationTime !== 'number') {
+ if ( !this.expirationTime || typeof this.expirationTime !== 'number' ) {
this.log.error('Captcha service self-test failed: invalid expiration time configuration');
return false;
}
-
+
// Basic functionality test - generate a test captcha and verify storage
- const testToken = 'test-' + this.crypto.randomBytes(8).toString('hex');
+ const testToken = `test-${ this.crypto.randomBytes(8).toString('hex')}`;
const testText = 'testcaptcha';
-
+
// Store the test captcha
this.captchaTokens.set(testToken, {
text: testText,
- expiresAt: Date.now() + this.expirationTime
+ expiresAt: Date.now() + this.expirationTime,
});
-
+
// Verify the test captcha
const correctVerification = this.verifyCaptcha(testToken, testText);
-
+
// Check if verification worked and token was removed
- if (!correctVerification || this.captchaTokens.has(testToken)) {
+ if ( !correctVerification || this.captchaTokens.has(testToken) ) {
this.log.error('Captcha service self-test failed: verification test failed');
return false;
}
-
+
this.log.debug('Captcha service self-test passed');
return true;
- } catch (error) {
+ } catch ( error ) {
this.log.error(`Captcha service self-test failed with error: ${error.message}`);
return false;
}
@@ -625,7 +625,7 @@ class CaptchaService extends BaseService {
* Returns the service's diagnostic information
* @returns {Object} Diagnostic information about the service
*/
- getDiagnosticInfo() {
+ getDiagnosticInfo () {
return {
serviceId: this.serviceId,
enabled: this.enabled,
@@ -635,14 +635,14 @@ class CaptchaService extends BaseService {
enabled: this.enabled,
difficulty: this.difficulty,
expirationTime: this.expirationTime,
- testMode: this.testMode
+ testMode: this.testMode,
},
processId: process.pid,
- testTokenExists: this.captchaTokens.has('test-static-token')
+ testTokenExists: this.captchaTokens.has('test-static-token'),
};
}
}
// Export both as a named export and as a default export for compatibility
module.exports = CaptchaService;
-module.exports.CaptchaService = CaptchaService;
\ No newline at end of file
+module.exports.CaptchaService = CaptchaService;
\ No newline at end of file
diff --git a/src/backend/src/modules/core/AlarmService.d.ts b/src/backend/src/modules/core/AlarmService.d.ts
index 1659cd7c8a..a838aaf08d 100644
--- a/src/backend/src/modules/core/AlarmService.d.ts
+++ b/src/backend/src/modules/core/AlarmService.d.ts
@@ -1,6 +1,6 @@
export class AlarmService {
- create(id: string, message: string, fields?: object): void;
- clear(id: string): void;
- get_alarm(id: string): object | undefined;
+ create (id: string, message: string, fields?: object): void;
+ clear (id: string): void;
+ get_alarm (id: string): object | undefined;
// Add more methods/properties as needed for MeteringService usage
}
\ No newline at end of file
diff --git a/src/backend/src/modules/core/AlarmService.js b/src/backend/src/modules/core/AlarmService.js
index d87350557a..4636801473 100644
--- a/src/backend/src/modules/core/AlarmService.js
+++ b/src/backend/src/modules/core/AlarmService.js
@@ -26,7 +26,6 @@ const fs = require('fs');
const BaseService = require('../../services/BaseService.js');
-
/**
* AlarmService class is responsible for managing alarms.
* It provides methods for creating, clearing, and handling alarms.
@@ -37,7 +36,7 @@ class AlarmService extends BaseService {
identutil: 'core.util.identutil',
stdioutil: 'core.util.stdioutil',
Context: 'core.context',
- }
+ };
/**
* This method initializes the AlarmService by setting up its internal data structures and initializing any required dependencies.
*
@@ -72,17 +71,17 @@ class AlarmService extends BaseService {
const lines = [];
for ( const alarm of Object.values(this.alarms) ) {
const line =
- `\x1B[31;1m [alarm]\x1B[0m ` +
+ '\x1B[31;1m [alarm]\x1B[0m ' +
`${alarm.id_string}: ${alarm.message} (${alarm.count})`;
const line_lines = this.stdioutil.split_lines(line);
lines.push(...line_lines);
}
return lines;
- }
+ };
}
}
-
+
/**
* AlarmService registers its commands at the consolidation phase because
* the '_init' method of CommandService may not have been called yet.
@@ -106,7 +105,7 @@ class AlarmService extends BaseService {
* Method to create an alarm with the given ID, message, and fields.
* If the ID already exists, it will be updated with the new fields
* and the occurrence count will be incremented.
- *
+ *
* @param {string} id - Unique identifier for the alarm.
* @param {string} message - Message associated with the alarm.
* @param {object} fields - Additional information about the alarm.
@@ -156,7 +155,7 @@ class AlarmService extends BaseService {
*/
get () {
return alarm.timestamps?.length ?? 0;
- }
+ },
});
Object.defineProperty(alarm, 'id_string', {
@@ -173,10 +172,10 @@ class AlarmService extends BaseService {
return alarm.id;
}
- const truncatedLongId = alarm.id.slice(0, 20) + '...';
+ const truncatedLongId = `${alarm.id.slice(0, 20) }...`;
return `${alarm.short_id} (${truncatedLongId})`;
- }
+ },
});
return alarm;
@@ -223,7 +222,7 @@ class AlarmService extends BaseService {
*/
clear (id) {
const alarm = this.alarms[id];
- if ( !alarm ) {
+ if ( ! alarm ) {
return;
}
delete this.alarms[id];
@@ -241,7 +240,7 @@ class AlarmService extends BaseService {
}
}
return true;
- }
+ };
const rule_actions = {
'no-alert': () => alarm.no_alert = true,
@@ -257,12 +256,9 @@ class AlarmService extends BaseService {
}
}
-
handle_alarm_repeat_ (alarm) {
- this.log.warn(
- `REPEAT ${alarm.id_string} :: ${alarm.message} (${alarm.count})`,
- alarm.fields,
- );
+ this.log.warn(`REPEAT ${alarm.id_string} :: ${alarm.message} (${alarm.count})`,
+ alarm.fields);
this.apply_known_errors_(alarm);
@@ -276,27 +272,25 @@ class AlarmService extends BaseService {
}
this.pager.alert({
- id: (alarm.id ?? 'something-bad') + '-r_${alarm.count}',
+ id: `${alarm.id ?? 'something-bad' }-r_\${alarm.count}`,
message: alarm.message ?? alarm.id ?? 'something bad happened',
source: 'alarm-service',
severity,
custom: {
fields: fields_clean,
trace: alarm.error?.stack,
- }
+ },
});
}
handle_alarm_on_ (alarm) {
- this.log.error(
- `ACTIVE ${alarm.id_string} :: ${alarm.message} (${alarm.count})`,
- alarm.fields,
- );
+ this.log.error(`ACTIVE ${alarm.id_string} :: ${alarm.message} (${alarm.count})`,
+ alarm.fields);
this.apply_known_errors_(alarm);
// dev console
- if ( this.global_config.env === 'dev' && ! this.attached_dev ) {
+ if ( this.global_config.env === 'dev' && !this.attached_dev ) {
this.attached_dev = true;
const svc_devConsole = this.services.get('dev-console');
svc_devConsole.turn_on_the_warning_lights();
@@ -329,7 +323,7 @@ class AlarmService extends BaseService {
custom: {
fields: fields_clean,
trace: alarm.error?.stack,
- }
+ },
});
// Write a .log file for the alert that happened
@@ -347,7 +341,7 @@ class AlarmService extends BaseService {
(async () => {
try {
- fs.appendFileSync(`alert_${alarm.id}.log`, alert_info + '\n');
+ fs.appendFileSync(`alert_${alarm.id}.log`, `${alert_info }\n`);
} catch (e) {
this.log.error(`failed to write alert log: ${e.message}`);
}
@@ -358,17 +352,15 @@ class AlarmService extends BaseService {
}
handle_alarm_off_ (alarm) {
- this.log.info(
- `CLEAR ${alarm.id} :: ${alarm.message} (${alarm.count})`,
- alarm.fields,
- );
+ this.log.info(`CLEAR ${alarm.id} :: ${alarm.message} (${alarm.count})`,
+ alarm.fields);
}
/**
* Method to get an alarm by its ID.
- *
+ *
* @param {*} id - The ID of the alarm to get.
- * @returns
+ * @returns
*/
get_alarm (id) {
return this.alarms[id] ?? this.alarm_aliases[id];
@@ -380,7 +372,7 @@ class AlarmService extends BaseService {
// This function is responsible for processing specific events related to alarms.
// It can be used for tasks such as updating alarm status, sending notifications, or triggering actions.
// This function is called internally by the AlarmService class.
-
+
// /*
// * handleAlarmEvent - Handles a specific alarm event.
// *
@@ -392,8 +384,10 @@ class AlarmService extends BaseService {
// }
const completeAlarmID = (args) => {
// The alarm ID is the first argument, so return no results if we're on the second or later.
- if (args.length > 1)
+ if ( args.length > 1 )
+ {
return;
+ }
const lastArg = args[args.length - 1];
const results = [];
@@ -416,7 +410,7 @@ class AlarmService extends BaseService {
for ( const alarm of Object.values(this.alarms) ) {
log.log(`${alarm.id_string}: ${alarm.message} (${alarm.count})`);
}
- }
+ },
},
{
id: 'info',
@@ -424,7 +418,7 @@ class AlarmService extends BaseService {
handler: async (args, log) => {
const [id] = args;
const alarm = this.get_alarm(id);
- if ( !alarm ) {
+ if ( ! alarm ) {
log.log(`no alarm with id ${id}`);
return;
}
@@ -451,10 +445,8 @@ class AlarmService extends BaseService {
const [id] = args;
const alarm = this.get_alarm(id);
if ( ! alarm ) {
- log.log(
- `no alarm with id ${id}; ` +
- `but calling clear(${JSON.stringify(id)}) anyway.`
- );
+ log.log(`no alarm with id ${id}; ` +
+ `but calling clear(${JSON.stringify(id)}) anyway.`);
}
this.clear(id);
},
@@ -469,7 +461,7 @@ class AlarmService extends BaseService {
for ( const alarm of alarms ) {
this.handle_alarm_off_(alarm);
}
- }
+ },
},
{
id: 'sound',
@@ -477,7 +469,7 @@ class AlarmService extends BaseService {
handler: async (args, log) => {
const [id, message] = args;
this.create(id ?? 'test', message, {});
- }
+ },
},
{
id: 'inspect',
@@ -485,18 +477,18 @@ class AlarmService extends BaseService {
handler: async (args, log) => {
const [id, occurance_idx] = args;
const alarm = this.get_alarm(id);
- if ( !alarm ) {
+ if ( ! alarm ) {
log.log(`no alarm with id ${id}`);
return;
}
const occurance = alarm.occurrences[occurance_idx];
- if ( !occurance ) {
+ if ( ! occurance ) {
log.log(`no occurance with index ${occurance_idx}`);
return;
}
log.log(`┏━━ Logs before: ${alarm.id_string} ━━━━`);
for ( const lg of occurance.logs ) {
- log.log("┃ " + this.logutil.stringify_log_entry(lg));
+ log.log(`┃ ${ this.logutil.stringify_log_entry(lg)}`);
}
log.log(`┗━━ Logs before: ${alarm.id_string} ━━━━`);
},
diff --git a/src/backend/src/modules/core/ContextService.js b/src/backend/src/modules/core/ContextService.js
index 6304bc342d..2494bbcb2c 100644
--- a/src/backend/src/modules/core/ContextService.js
+++ b/src/backend/src/modules/core/ContextService.js
@@ -1,29 +1,29 @@
/*
* 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 .
*/
-const BaseService = require("../../services/BaseService");
-const { Context } = require("../../util/context");
+const BaseService = require('../../services/BaseService');
+const { Context } = require('../../util/context');
/**
* ContextService provides a way for other services to register a hook to be
* called when a context/subcontext is created.
- *
+ *
* Contexts are used to provide contextual information in the execution
* context (dynamic scope). They can also be used to identify a "span";
* a span is a labelled frame of execution that can be used to track
diff --git a/src/backend/src/modules/core/Core2Module.js b/src/backend/src/modules/core/Core2Module.js
index ea5e688bad..d60bbdb309 100644
--- a/src/backend/src/modules/core/Core2Module.js
+++ b/src/backend/src/modules/core/Core2Module.js
@@ -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 .
*/
-const { AdvancedBase } = require("@heyputer/putility");
+const { AdvancedBase } = require('@heyputer/putility');
/**
* A replacement for CoreModule with as few external relative requires as possible.
* This will eventually be the successor to CoreModule, the main module for Puter's backend.
- *
+ *
* The scope of this module is:
* - logging and error handling
* - alarm handling
@@ -40,36 +40,36 @@ class Core2Module extends AdvancedBase {
for ( const k in lib ) {
useapi.def(`core.${k}`, lib[k], { assign: true });
}
-
+
useapi.def('core.context', require('../../util/context.js').Context);
-
+
// === SERVICES === //
const services = context.get('services');
const { LogService } = require('./LogService.js');
services.registerService('log-service', LogService);
-
- const { AlarmService } = require("./AlarmService.js");
+
+ const { AlarmService } = require('./AlarmService.js');
services.registerService('alarm', AlarmService);
-
- const { ErrorService } = require("./ErrorService.js");
+
+ const { ErrorService } = require('./ErrorService.js');
services.registerService('error-service', ErrorService);
-
- const { PagerService } = require("./PagerService.js");
+
+ const { PagerService } = require('./PagerService.js');
services.registerService('pager', PagerService);
-
- const { ExpectationService } = require("./ExpectationService.js");
+
+ const { ExpectationService } = require('./ExpectationService.js');
services.registerService('expectations', ExpectationService);
- const { ProcessEventService } = require("./ProcessEventService.js");
+ const { ProcessEventService } = require('./ProcessEventService.js');
services.registerService('process-event', ProcessEventService);
-
- const { ServerHealthService } = require("./ServerHealthService.js");
+
+ const { ServerHealthService } = require('./ServerHealthService.js');
services.registerService('server-health', ServerHealthService);
-
- const { ParameterService } = require("./ParameterService.js");
+
+ const { ParameterService } = require('./ParameterService.js');
services.registerService('params', ParameterService);
-
+
const { ContextService } = require('./ContextService.js');
services.registerService('context', ContextService);
}
diff --git a/src/backend/src/modules/core/ErrorService.js b/src/backend/src/modules/core/ErrorService.js
index c5ed96bc23..7b2f7a7fba 100644
--- a/src/backend/src/modules/core/ErrorService.js
+++ b/src/backend/src/modules/core/ErrorService.js
@@ -17,8 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const BaseService = require("../../services/BaseService");
-
+const BaseService = require('../../services/BaseService');
/**
* **ErrorContext Class**
@@ -45,7 +44,6 @@ class ErrorContext {
}
}
-
/**
* The ErrorService class is responsible for handling and reporting errors within the system.
* It provides methods to initialize the service, create error contexts, and report errors with detailed logging and alarm mechanisms.
@@ -67,22 +65,22 @@ class ErrorService extends BaseService {
this.alarm = services.get('alarm');
this.backupLogger = services.get('log-service').create('error-service');
}
-
+
/**
* Creates an ErrorContext instance with the provided logging context.
- *
+ *
* @param {*} log_context The logging context to associate with the error reports.
* @returns {ErrorContext} An ErrorContext instance.
*/
create (log_context) {
return new ErrorContext(this, log_context);
}
-
+
/**
* Reports an error with the specified location and details.
* The "location" is a string up to the callers discretion to identify
* the source of the error.
- *
+ *
* @param {*} location The location where the error occurred.
* @param {*} fields The error details to report.
* @param {boolean} [alarm=true] Whether to raise an alarm for the error.
@@ -91,7 +89,7 @@ class ErrorService extends BaseService {
report (location, { source, logger, trace, extra, message }, alarm = true) {
message = message ?? source?.message;
logger = logger ?? this.backupLogger;
- logger.error(`Error @ ${location}: ${message}; ` + source?.stack);
+ logger.error(`Error @ ${location}: ${message}; ${ source?.stack}`);
if ( alarm ) {
const alarm_id = `${location}:${message}`;
diff --git a/src/backend/src/modules/core/ExpectationService.js b/src/backend/src/modules/core/ExpectationService.js
index ad192fe46b..795e177bd8 100644
--- a/src/backend/src/modules/core/ExpectationService.js
+++ b/src/backend/src/modules/core/ExpectationService.js
@@ -35,7 +35,7 @@ const BaseService = require('../../services/BaseService');
*/
class ExpectationService extends BaseService {
static USE = {
- expect: 'core.expect'
+ expect: 'core.expect',
};
/**
@@ -62,14 +62,14 @@ class ExpectationService extends BaseService {
handler: async (args, log) => {
this.purgeExpectations_();
if ( this.expectations_.length < 1 ) {
- log.log(`there are none`);
+ log.log('there are none');
return;
}
for ( const expectation of this.expectations_ ) {
expectation.report(log);
}
- }
- }
+ },
+ },
]);
}
@@ -91,14 +91,13 @@ class ExpectationService extends BaseService {
*
* @returns {void}
*/
-
+
// The comment should be placed above the method at line 68
setInterval(() => {
this.purgeExpectations_();
}, 1000);
}
-
/**
* Purges expectations that have been met.
*
@@ -121,7 +120,7 @@ class ExpectationService extends BaseService {
/**
* Registers an expectation to be tracked by the service.
- *
+ *
* @param {Object} workUnit - The work unit to track
* @param {string} checkpoint - The checkpoint to expect
* @returns {void}
@@ -131,8 +130,6 @@ class ExpectationService extends BaseService {
}
}
-
-
module.exports = {
- ExpectationService
+ ExpectationService,
};
\ No newline at end of file
diff --git a/src/backend/src/modules/core/LogService.js b/src/backend/src/modules/core/LogService.js
index 80d630b050..c262118729 100644
--- a/src/backend/src/modules/core/LogService.js
+++ b/src/backend/src/modules/core/LogService.js
@@ -54,7 +54,6 @@ const display_log_level_label = {
100: 'ALL',
};
-
/**
* Represents a logging context within the LogService.
* This class is used to manage logging operations with specific context information,
@@ -69,20 +68,28 @@ class LogContext {
}
sub (name, fields = {}) {
- return new LogContext(
- this.logService,
- {
- crumbs: name ? [...this.crumbs, name] : [...this.crumbs],
- fields: {...this.fields, ...fields},
- }
- );
+ return new LogContext(this.logService,
+ {
+ crumbs: name ? [...this.crumbs, name] : [...this.crumbs],
+ fields: { ...this.fields, ...fields },
+ });
}
- info (message, fields, objects) { this.log(LOG_LEVEL_INFO, message, fields, objects); }
- warn (message, fields, objects) { this.log(LOG_LEVEL_WARN, message, fields, objects); }
- debug (message, fields, objects) { this.log(LOG_LEVEL_DEBU, message, fields, objects); }
- error (message, fields, objects) { this.log(LOG_LEVEL_ERRO, message, fields, objects); }
- tick (message, fields, objects) { this.log(LOG_LEVEL_TICK, message, fields, objects); }
+ info (message, fields, objects) {
+ this.log(LOG_LEVEL_INFO, message, fields, objects);
+ }
+ warn (message, fields, objects) {
+ this.log(LOG_LEVEL_WARN, message, fields, objects);
+ }
+ debug (message, fields, objects) {
+ this.log(LOG_LEVEL_DEBU, message, fields, objects);
+ }
+ error (message, fields, objects) {
+ this.log(LOG_LEVEL_ERRO, message, fields, objects);
+ }
+ tick (message, fields, objects) {
+ this.log(LOG_LEVEL_TICK, message, fields, objects);
+ }
called (fields = {}) {
this.log(LOG_LEVEL_DEBU, 'called', fields);
}
@@ -94,11 +101,9 @@ class LogContext {
}
cache (isCacheHit, identifier, fields = {}) {
- this.log(
- LOG_LEVEL_DEBU,
- isCacheHit ? 'cache_hit' : 'cache_miss',
- { identifier, ...fields },
- );
+ this.log(LOG_LEVEL_DEBU,
+ isCacheHit ? 'cache_hit' : 'cache_miss',
+ { identifier, ...fields });
}
log (log_level, message, fields = {}, objects = {}) {
@@ -108,7 +113,7 @@ class LogContext {
if ( x && x.get('trace_request') ) {
fields.trace_request = x.get('trace_request');
}
- if ( ! fields.actor && x && x.get('actor') ) {
+ if ( !fields.actor && x && x.get('actor') ) {
try {
fields.actor = x.get('actor');
} catch (e) {
@@ -124,20 +129,19 @@ class LogContext {
}
if ( Context.get('injected_logger', { allow_fallback: true }) ) {
Context.get('injected_logger').log(
- message + (fields ? ('; fields: ' + JSON.stringify(fields)) : ''),
- );
+ message + (fields ? (`; fields: ${ JSON.stringify(fields)}`) : ''));
}
- this.logService.log_(
- log_level,
- this.crumbs,
- message, fields, objects,
- );
+ this.logService.log_(log_level,
+ this.crumbs,
+ message,
+ fields,
+ objects);
}
/**
* Generates a human-readable trace ID for logging purposes.
- *
- * @returns {string} A trace ID in the format 'xxxxxx-xxxxxx' where each segment is a
+ *
+ * @returns {string} A trace ID in the format 'xxxxxx-xxxxxx' where each segment is a
* random string of six lowercase letters and digits.
*/
mkid () {
@@ -158,7 +162,6 @@ class LogContext {
return this;
}
-
/**
* Gets the log buffer maintained by the LogService. This shows the most
* recent log entries.
@@ -176,10 +179,10 @@ class LogContext {
/**
* @class DevLogger
* @classdesc
-* A development logger class designed for logging messages during development.
-* This logger can either log directly to console or delegate logging to another logger.
+* A development logger class designed for logging messages during development.
+* This logger can either log directly to console or delegate logging to another logger.
* It provides functionality to turn logging on/off, and can optionally write logs to a file.
-*
+*
* @param {function} log - The logging function, typically `console.log` or similar.
* @param {object} [opt_delegate] - An optional logger to which log messages can be delegated.
*/
@@ -196,35 +199,36 @@ class DevLogger {
}
onLogMessage (log_lvl, crumbs, message, fields, objects) {
if ( this.delegate ) {
- this.delegate.onLogMessage(
- log_lvl, crumbs, message, fields, objects,
- );
+ this.delegate.onLogMessage(log_lvl, crumbs, message, fields, objects);
}
if ( this.off ) return;
-
- if ( ! process.env.DEBUG && log_lvl.ordinal > display_log_level ) return;
- const ld = Context.get('logdent', { allow_fallback: true })
+ if ( !process.env.DEBUG && log_lvl.ordinal > display_log_level ) return;
+
+ const ld = Context.get('logdent', { allow_fallback: true });
const prefix = globalThis.dev_console_indent_on
? Array(ld ?? 0).fill(' ').join('')
: '';
this.log_(stringify_log_entry({
prefix,
- log_lvl, crumbs, message, fields, objects,
+ log_lvl,
+ crumbs,
+ message,
+ fields,
+ objects,
}));
}
-
+
log_ (text) {
if ( this.recto ) {
const fs = require('node:fs');
- fs.appendFileSync(this.recto, text + '\n');
+ fs.appendFileSync(this.recto, `${text }\n`);
}
this.log(text);
}
}
-
/**
* @class NullLogger
* @description A logger that does nothing, effectively disabling logging.
@@ -244,10 +248,9 @@ class NullLogger {
}
}
-
/**
* WinstonLogger Class
-*
+*
* A logger that delegates log messages to a Winston logger instance.
*/
class WinstonLogger {
@@ -264,13 +267,12 @@ class WinstonLogger {
}
}
-
/**
* @class TimestampLogger
* @classdesc A logger that adds timestamps to log messages before delegating them to another logger.
* This class wraps another logger instance to ensure that all log messages include a timestamp,
* which can be useful for tracking the sequence of events in a system.
-*
+*
* @param {Object} delegate - The logger instance to which the timestamped log messages are forwarded.
*/
class TimestampLogger {
@@ -283,7 +285,6 @@ class TimestampLogger {
}
}
-
/**
* The `BufferLogger` class extends the logging functionality by maintaining a buffer of log entries.
* This class is designed to:
@@ -307,7 +308,6 @@ class BufferLogger {
}
}
-
/**
* Represents a custom logger that can modify log messages before they are passed to another logger.
* @class CustomLogger
@@ -331,25 +331,27 @@ class CustomLogger {
try {
ret = await this.callback({
context,
- log_lvl, crumbs, message, fields, args: a,
+ log_lvl,
+ crumbs,
+ message,
+ fields,
+ args: a,
});
} catch (e) {
console.error('error?', e);
}
-
+
if ( ret && ret.skip ) return;
if ( ! ret ) {
- this.delegate.onLogMessage(
- log_lvl,
- crumbs,
- message,
- fields,
- ...a,
- );
+ this.delegate.onLogMessage(log_lvl,
+ crumbs,
+ message,
+ fields,
+ ...a);
return;
}
-
+
const {
log_lvl: _log_lvl,
crumbs: _crumbs,
@@ -358,27 +360,24 @@ class CustomLogger {
args,
} = ret;
- this.delegate.onLogMessage(
- _log_lvl ?? log_lvl,
- _crumbs ?? crumbs,
- _message ?? message,
- _fields ?? fields,
- ...(args ?? a ?? []),
- );
+ this.delegate.onLogMessage(_log_lvl ?? log_lvl,
+ _crumbs ?? crumbs,
+ _message ?? message,
+ _fields ?? fields,
+ ...(args ?? a ?? []));
}
}
-
/**
-* The `LogService` class extends `BaseService` and is responsible for managing and
-* orchestrating various logging functionalities within the application. It handles
-* log initialization, middleware registration, log directory management, and
+* The `LogService` class extends `BaseService` and is responsible for managing and
+* orchestrating various logging functionalities within the application. It handles
+* log initialization, middleware registration, log directory management, and
* provides methods for creating log contexts and managing log output levels.
*/
class LogService extends BaseService {
static MODULES = {
path: require('path'),
- }
+ };
/**
* Defines the modules required by the LogService class.
* This static property contains modules that are used for file path operations.
@@ -389,7 +388,7 @@ class LogService extends BaseService {
this.loggers = [];
this.bufferLogger = null;
}
-
+
/**
* Registers a custom logging middleware with the LogService.
* @param {*} callback - The callback function that modifies log parameters before delegation.
@@ -397,7 +396,7 @@ class LogService extends BaseService {
register_log_middleware (callback) {
this.loggers[0] = new CustomLogger(this.loggers[0], callback);
}
-
+
/**
* Registers logging commands with the command service.
*/
@@ -408,20 +407,20 @@ class LogService extends BaseService {
id: 'show',
description: 'toggle log output',
handler: async (args, log) => {
- this.devlogger && (this.devlogger.off = ! this.devlogger.off);
- }
+ this.devlogger && (this.devlogger.off = !this.devlogger.off);
+ },
},
{
id: 'rec',
description: 'start recording to a file via dev logger',
handler: async (args, ctx) => {
const [name] = args;
- const {log} = ctx;
+ const { log } = ctx;
if ( ! this.devlogger ) {
log('no dev logger; what are you doing?');
}
this.devlogger.recto = name;
- }
+ },
},
{
id: 'stop',
@@ -431,15 +430,15 @@ class LogService extends BaseService {
log('no dev logger; what are you doing?');
}
this.devlogger.recto = null;
- }
+ },
},
{
id: 'indent',
description: 'toggle log indentation',
handler: async (args, log) => {
globalThis.dev_console_indent_on =
- ! globalThis.dev_console_indent_on;
- }
+ !globalThis.dev_console_indent_on;
+ },
},
{
id: 'get-level',
@@ -460,11 +459,11 @@ class LogService extends BaseService {
}
/**
* Registers logging commands with the command service.
- *
+ *
* This method sets up various logging commands that can be used to
* interact with the log output, such as toggling log display,
* starting/stopping log recording, and toggling log indentation.
- *
+ *
* @memberof LogService
*/
async _init () {
@@ -475,8 +474,8 @@ class LogService extends BaseService {
let logger;
if ( ! config.no_winston )
- logger = new WinstonLogger(
- winston.createLogger({
+ {
+ logger = new WinstonLogger(winston.createLogger({
levels: WINSTON_LEVELS,
transports: [
new winston.transports.DailyRotateFile({
@@ -485,8 +484,8 @@ class LogService extends BaseService {
zippedArchive: true,
maxSize: '20m',
- // TODO: uncomment when we have a log backup strategy
- // maxFiles: '14d',
+ // TODO: uncomment when we have a log backup strategy
+ // maxFiles: '14d',
}),
new winston.transports.DailyRotateFile({
level: 'error',
@@ -495,8 +494,8 @@ class LogService extends BaseService {
zippedArchive: true,
maxSize: '20m',
- // TODO: uncomment when we have a log backup strategy
- // maxFiles: '14d',
+ // TODO: uncomment when we have a log backup strategy
+ // maxFiles: '14d',
}),
new winston.transports.DailyRotateFile({
level: 'system',
@@ -505,18 +504,18 @@ class LogService extends BaseService {
zippedArchive: true,
maxSize: '20m',
- // TODO: uncomment when we have a log backup strategy
- // maxFiles: '14d',
+ // TODO: uncomment when we have a log backup strategy
+ // maxFiles: '14d',
}),
],
- }),
- );
+ }));
+ }
if ( config.env === 'dev' ) {
logger = config.flag_no_logs // useful for profiling
? new NullLogger()
: new DevLogger(console.log.bind(console), logger);
-
+
this.devlogger = logger;
}
@@ -565,10 +564,10 @@ class LogService extends BaseService {
if ( ! globalThis.original_console_object ) {
globalThis.original_console_object = console;
}
-
+
// Keep console prototype
const logconsole = Object.create(console);
-
+
// Override simple log functions
const logfn = level => (...a) => {
logger[level](a.map(arg => {
@@ -579,26 +578,24 @@ class LogService extends BaseService {
logconsole.log = logfn('info');
logconsole.warn = logfn('warn');
logconsole.error = logfn('error');
-
+
globalThis.console = logconsole;
}
}
/**
* Create a new log context with the specified prefix
- *
+ *
* @param {1} prefix - The prefix for the log context
* @param {*} fields - Optional fields to include in the log context
* @returns {LogContext} A new log context with the specified prefix and fields
*/
create (prefix, fields = {}) {
- const logContext = new LogContext(
- this,
- {
- crumbs: [prefix],
- fields,
- },
- );
+ const logContext = new LogContext(this,
+ {
+ crumbs: [prefix],
+ fields,
+ });
return logContext;
}
@@ -607,15 +604,13 @@ class LogService extends BaseService {
try {
// skip messages that are above the output level
if ( log_lvl.ordinal > this.output_lvl ) return;
-
+
if ( this.config.trace_logs ) {
fields.stack = (new Error('logstack')).stack;
}
for ( const logger of this.loggers ) {
- logger.onLogMessage(
- log_lvl, crumbs, message, fields, objects,
- );
+ logger.onLogMessage(log_lvl, crumbs, message, fields, objects);
}
} catch (e) {
// If logging fails, we don't want anything to happen
@@ -629,13 +624,12 @@ class LogService extends BaseService {
}
}
-
/**
* Ensures that a log directory exists for logging purposes.
* This method attempts to create or locate a directory for log files,
* falling back through several predefined paths if the preferred
* directory does not exist or cannot be created.
- *
+ *
* @throws {Error} If no suitable log directory can be found or created.
*/
ensure_log_directory_ () {
@@ -689,7 +683,7 @@ class LogService extends BaseService {
/**
* Generates a sanitized file path for log files.
- *
+ *
* @param {string} name - The name of the log file, which will be sanitized to remove any path characters.
* @returns {string} A sanitized file path within the log directory.
*/
@@ -699,11 +693,10 @@ class LogService extends BaseService {
return this.modules.path.join(this.log_directory, name);
}
-
/**
* Get the most recent log entries from the buffer maintained by the LogService.
* By default, the buffer contains the last 20 log entries.
- * @returns
+ * @returns
*/
get_log_buffer () {
return this.bufferLogger.buffer;
@@ -712,5 +705,5 @@ class LogService extends BaseService {
module.exports = {
LogService,
- stringify_log_entry
+ stringify_log_entry,
};
\ No newline at end of file
diff --git a/src/backend/src/modules/core/PagerService.js b/src/backend/src/modules/core/PagerService.js
index 99733805c8..7b24dded24 100644
--- a/src/backend/src/modules/core/PagerService.js
+++ b/src/backend/src/modules/core/PagerService.js
@@ -21,7 +21,6 @@ const pdjs = require('@pagerduty/pdjs');
const BaseService = require('../../services/BaseService');
const util = require('util');
-
/**
* @class PagerService
* @extends BaseService
@@ -34,14 +33,14 @@ const util = require('util');
class PagerService extends BaseService {
static USE = {
Context: 'core.context',
- }
-
+ };
+
async _construct () {
this.config = this.global_config.pager;
this.alertHandlers_ = [];
}
-
+
/**
* PagerService registers its commands at the consolidation phase because
* the '_init' method of CommandService may not have been called yet.
@@ -123,7 +122,6 @@ class PagerService extends BaseService {
}
}
-
/**
* Sends an alert to all registered alert handlers.
*
@@ -155,9 +153,9 @@ class PagerService extends BaseService {
source: 'test',
severity,
});
- }
- }
- ])
+ },
+ },
+ ]);
}
}
diff --git a/src/backend/src/modules/core/ParameterService.js b/src/backend/src/modules/core/ParameterService.js
index cbd9d53649..f011cd81f4 100644
--- a/src/backend/src/modules/core/ParameterService.js
+++ b/src/backend/src/modules/core/ParameterService.js
@@ -1,24 +1,24 @@
// METADATA // {"ai-commented":{"service":"claude"}}
/*
* 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 .
*/
-const BaseService = require("../../services/BaseService");
+const BaseService = require('../../services/BaseService');
/**
* @class ParameterService
@@ -33,7 +33,6 @@ class ParameterService extends BaseService {
/** @type {Array} */
this.parameters_ = [];
}
-
/**
* Initializes the service by registering commands with the command service.
@@ -45,31 +44,28 @@ class ParameterService extends BaseService {
this._registerCommands(this.services.get('commands'));
}
- createParameters(serviceName, parameters, opt_instance) {
- for (const parameter of parameters) {
+ createParameters (serviceName, parameters, opt_instance) {
+ for ( const parameter of parameters ) {
this.log.debug(`registering parameter ${serviceName}:${parameter.id}`);
this.parameters_.push(new Parameter({
...parameter,
id: `${serviceName}:${parameter.id}`,
}));
if ( opt_instance ) {
- this.bindToInstance(
- `${serviceName}:${parameter.id}`,
- opt_instance,
- parameter.id,
- );
+ this.bindToInstance(`${serviceName}:${parameter.id}`,
+ opt_instance,
+ parameter.id);
}
}
}
-
/**
* Gets the value of a parameter by its ID
* @param {string} id - The unique identifier of the parameter to retrieve
* @returns {Promise<*>} The current value of the parameter
* @throws {Error} If parameter with given ID is not found
*/
- async get(id) {
+ async get (id) {
const parameter = this._get_param(id);
return await parameter.get();
}
@@ -84,7 +80,7 @@ class ParameterService extends BaseService {
return parameter.subscribe(listener);
}
- _get_param(id) {
+ _get_param (id) {
const parameter = this.parameters_.find(p => p.spec_.id === id);
if ( ! parameter ) {
throw new Error(`unknown parameter: ${id}`);
@@ -99,8 +95,10 @@ class ParameterService extends BaseService {
_registerCommands (commands) {
const completeParameterName = (args) => {
// The parameter name is the first argument, so return no results if we're on the second or later.
- if (args.length > 1)
+ if ( args.length > 1 )
+ {
return;
+ }
const lastArg = args[args.length - 1];
return this.parameters_
@@ -143,20 +141,19 @@ class ParameterService extends BaseService {
log.log(`available parameters${
prefix ? ` (starting with: ${prefix})` : ''
}:`);
- for (const parameter of parameters) {
+ for ( const parameter of parameters ) {
// log.log(`- ${parameter.spec_.id}: ${parameter.spec_.description}`);
// Log parameter description and value
const value = await parameter.get();
log.log(`- ${parameter.spec_.id} = ${value}`);
log.log(` ${parameter.spec_.description}`);
}
- }
- }
+ },
+ },
]);
}
}
-
/**
* @class Parameter
* @description Represents a configurable parameter with value management, constraints, and change notification capabilities.
@@ -164,7 +161,7 @@ class ParameterService extends BaseService {
* Supports validation through configurable constraints and maintains a list of value change listeners.
*/
class Parameter {
- constructor(spec) {
+ constructor (spec) {
this.spec_ = spec;
this.valueListeners_ = [];
@@ -173,7 +170,6 @@ class Parameter {
}
}
-
/**
* Sets a new value for the parameter after validating against constraints
* @param {*} value - The new value to set for the parameter
@@ -195,7 +191,6 @@ class Parameter {
}
}
-
/**
* Gets the current value of this parameter
* @returns {Promise<*>} The parameter's current value
diff --git a/src/backend/src/modules/core/ProcessEventService.js b/src/backend/src/modules/core/ProcessEventService.js
index 7a55568056..adac88f376 100644
--- a/src/backend/src/modules/core/ProcessEventService.js
+++ b/src/backend/src/modules/core/ProcessEventService.js
@@ -18,21 +18,21 @@
* along with this program. If not, see .
*/
-const BaseService = require("../../services/BaseService");
+const BaseService = require('../../services/BaseService');
/**
* Service class that handles process-wide events and errors.
* Provides centralized error handling for uncaught exceptions and unhandled promise rejections.
* Sets up event listeners on the process object to capture and report critical errors
* through the logging and error reporting services.
-*
+*
* @class ProcessEventService
*/
class ProcessEventService extends BaseService {
static USE = {
Context: 'core.context',
};
-
+
_init () {
const services = this.services;
const log = services.get('log-service').create('process-event-service');
@@ -44,7 +44,7 @@ class ProcessEventService extends BaseService {
* Sets up an event listener that reports errors when uncaught exceptions occur
* @param {Error} err - The uncaught exception error object
* @param {string} origin - The origin of the uncaught exception
- * @returns {Promise}
+ * @returns {Promise}
*/
await this.Context.allow_fallback(async () => {
errors.report('process:uncaughtException', {
diff --git a/src/backend/src/modules/core/ServerHealthService.js b/src/backend/src/modules/core/ServerHealthService.js
index 7846e48aa3..0e1717fe13 100644
--- a/src/backend/src/modules/core/ServerHealthService.js
+++ b/src/backend/src/modules/core/ServerHealthService.js
@@ -17,9 +17,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const BaseService = require("../../services/BaseService");
-const { time, promise } = require("@heyputer/putility").libs;
-
+const BaseService = require('../../services/BaseService');
+const { time, promise } = require('@heyputer/putility').libs;
/**
* The ServerHealthService class provides comprehensive health monitoring for the server.
@@ -28,15 +27,15 @@ const { time, promise } = require("@heyputer/putility").libs;
* - Managing health check results and failures
* - Triggering alarms for critical conditions
* - Logging and managing statistics for health metrics
-*
+*
* This service is designed to work primarily on Linux systems, reading system metrics
* from `/proc/meminfo` and handling alarms via an external 'alarm' service.
*/
class ServerHealthService extends BaseService {
static USE = {
- linuxutil: 'core.util.linuxutil'
+ linuxutil: 'core.util.linuxutil',
};
-
+
/**
* Defines the modules used by ServerHealthService.
* This static property is used to initialize and access system modules required for health checks.
@@ -45,12 +44,12 @@ class ServerHealthService extends BaseService {
*/
static MODULES = {
fs: require('fs'),
- }
+ };
/**
* Initializes the internal checks and failure tracking for the service.
* This method sets up empty arrays to store health checks and their failure statuses.
- *
+ *
* @private
*/
_construct () {
@@ -73,7 +72,6 @@ class ServerHealthService extends BaseService {
`/proc/meminfo` directly.
*/
-
const min_available_KiB = 1024 * 1024 * 2; // 2 GiB
const svc_alarm = this.services.get('alarm');
@@ -84,28 +82,25 @@ class ServerHealthService extends BaseService {
if ( process.platform !== 'linux' ) {
return;
}
-
- if ( this.config.no_system_checks ) return;
+ if ( this.config.no_system_checks ) return;
/**
* Adds a health check to the service.
- *
+ *
* @param {string} name - The name of the health check.
* @param {Function} fn - The function to execute for the health check.
* @returns {Object} A chainable object to add failure handlers.
*/
this.add_check('ram-usage', async () => {
- const meminfo_text = await this.modules.fs.promises.readFile(
- '/proc/meminfo', 'utf8'
- );
+ const meminfo_text = await this.modules.fs.promises.readFile('/proc/meminfo', 'utf8');
const meminfo = this.linuxutil.parse_meminfo(meminfo_text);
const log_fields = {
mem_free: meminfo.MemFree,
mem_available: meminfo.MemAvailable,
mem_total: meminfo.MemTotal,
};
-
+
this.log.debug('memory', log_fields);
Object.assign(this.stats_, log_fields);
@@ -116,12 +111,11 @@ class ServerHealthService extends BaseService {
});
}
-
/**
* Initializes service health checks by setting up periodic checks.
* This method configures an interval-based execution of health checks,
* handles timeouts, and manages failure states.
- *
+ *
* @param {none} - This method does not take any parameters.
* @returns {void} - This method does not return any value.
*/
@@ -129,11 +123,11 @@ class ServerHealthService extends BaseService {
const svc_alarm = this.services.get('alarm');
/**
* Initializes periodic health checks for the server.
- *
+ *
* This method sets up an interval to run all registered health checks
* at a specified frequency. It manages the execution of checks, handles
* timeouts, and logs errors or triggers alarms when checks fail.
- *
+ *
* @private
* @method init_service_checks_
* @memberof ServerHealthService
@@ -147,7 +141,7 @@ class ServerHealthService extends BaseService {
const p_timeout = new promise.TeePromise();
/**
* Creates a TeePromise to handle potential timeouts during health checks.
- *
+ *
* @returns {Promise} A promise that can be resolved or rejected from multiple places.
*/
const timeout = setTimeout(() => {
@@ -166,14 +160,12 @@ class ServerHealthService extends BaseService {
return;
}
- svc_alarm.create(
- 'health-check-failure',
- `Health check ${name} failed`,
- { error: err }
- );
+ svc_alarm.create('health-check-failure',
+ `Health check ${name} failed`,
+ { error: err });
check_failures.push({ name });
-
- this.log.error(`Error for healthcheck fail on ${name}: ` + err.stack);
+
+ this.log.error(`Error for healthcheck fail on ${name}: ${ err.stack}`);
// Run the on_fail handlers
for ( const fn of chainable.on_fail_ ) {
@@ -189,19 +181,16 @@ class ServerHealthService extends BaseService {
this.failures_ = check_failures;
}, 10 * time.SECOND, null, {
onBehindSchedule: (drift) => {
- svc_alarm.create(
- 'health-checks-behind-schedule',
- 'Health checks are behind schedule',
- { drift }
- );
- }
+ svc_alarm.create('health-checks-behind-schedule',
+ 'Health checks are behind schedule',
+ { drift });
+ },
});
}
-
/**
* Retrieves the current server health statistics.
- *
+ *
* @returns {Object} An object containing the current health statistics.
* This method returns a shallow copy of the internal `stats_` object to prevent
* direct manipulation of the service's data.
@@ -222,10 +211,9 @@ class ServerHealthService extends BaseService {
return chainable;
}
-
/**
* Retrieves the current health status of the server.
- *
+ *
* @returns {Object} An object containing:
* - `ok` {boolean}: Indicates if all health checks passed.
* - `failed` {Array}: An array of names of failed health checks, if any.
diff --git a/src/backend/src/modules/core/lib/__lib__.js b/src/backend/src/modules/core/lib/__lib__.js
index 43e858500f..05448a802c 100644
--- a/src/backend/src/modules/core/lib/__lib__.js
+++ b/src/backend/src/modules/core/lib/__lib__.js
@@ -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 .
*/
diff --git a/src/backend/src/modules/core/lib/expect.js b/src/backend/src/modules/core/lib/expect.js
index 8bf9cd6e8e..4992a20d59 100644
--- a/src/backend/src/modules/core/lib/expect.js
+++ b/src/backend/src/modules/core/lib/expect.js
@@ -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 .
*/
@@ -32,7 +32,7 @@ class WorkUnit {
*
* @class
*/
-
+
/**
* Creates and returns a new instance of WorkUnit.
*
@@ -52,7 +52,7 @@ class WorkUnit {
this.checkpoint_ = null;
}
checkpoint (label) {
- if ( ( global_config.logging ?? [] ).includes('checkpoint') ) {
+ if ( (global_config.logging ?? [] ).includes('checkpoint') ) {
console.log('CHECKPOINT', label);
}
this.checkpoint_ = label;
@@ -82,11 +82,9 @@ class CheckpointExpectation {
}
report (log) {
if ( this.check() ) return;
- log.log(
- `operation(${this.workUnit.id}): ` +
+ log.log(`operation(${this.workUnit.id}): ` +
`expected ${JSON.stringify(this.checkpoint)} ` +
- `and got ${JSON.stringify(this.workUnit.checkpoint_)}.`
- );
+ `and got ${JSON.stringify(this.workUnit.checkpoint_)}.`);
}
}
diff --git a/src/backend/src/modules/core/lib/identifier.js b/src/backend/src/modules/core/lib/identifier.js
index bd551e5e5f..2570e77ac6 100644
--- a/src/backend/src/modules/core/lib/identifier.js
+++ b/src/backend/src/modules/core/lib/identifier.js
@@ -18,18 +18,18 @@
* along with this program. If not, see .
*/
const adjectives = [
- 'amazing', 'ambitious', 'articulate', 'cool', 'bubbly', 'mindful', 'noble', 'savvy', 'serene',
+ 'amazing', 'ambitious', 'articulate', 'cool', 'bubbly', 'mindful', 'noble', 'savvy', 'serene',
'sincere', 'sleek', 'sparkling', 'spectacular', 'splendid', 'spotless', 'stunning',
'awesome', 'beaming', 'bold', 'brilliant', 'cheerful', 'modest', 'motivated',
'friendly', 'fun', 'funny', 'generous', 'gifted', 'graceful', 'grateful',
'passionate', 'patient', 'peaceful', 'perceptive', 'persistent',
- 'helpful', 'sensible', 'loyal', 'honest', 'clever', 'capable',
+ 'helpful', 'sensible', 'loyal', 'honest', 'clever', 'capable',
'calm', 'smart', 'genius', 'bright', 'charming', 'creative', 'diligent', 'elegant', 'fancy',
- 'colorful', 'avid', 'active', 'gentle', 'happy', 'intelligent',
+ 'colorful', 'avid', 'active', 'gentle', 'happy', 'intelligent',
'jolly', 'kind', 'lively', 'merry', 'nice', 'optimistic', 'polite',
- 'quiet', 'relaxed', 'silly', 'witty', 'young',
- 'strong', 'brave', 'agile', 'bold', 'confident', 'daring',
- 'fearless', 'heroic', 'mighty', 'powerful', 'valiant', 'wise', 'wonderful', 'zealous',
+ 'quiet', 'relaxed', 'silly', 'witty', 'young',
+ 'strong', 'brave', 'agile', 'bold', 'confident', 'daring',
+ 'fearless', 'heroic', 'mighty', 'powerful', 'valiant', 'wise', 'wonderful', 'zealous',
'warm', 'swift', 'neat', 'tidy', 'nifty', 'lucky', 'keen',
'blue', 'red', 'aqua', 'green', 'orange', 'pink', 'purple', 'cyan', 'magenta', 'lime',
'teal', 'lavender', 'beige', 'maroon', 'navy', 'olive', 'silver', 'gold', 'ivory',
@@ -40,13 +40,13 @@ const nouns = [
'magnet', 'chair', 'table', 'house', 'room', 'book', 'car', 'tree', 'candle', 'light', 'planet',
'flower', 'bird', 'fish', 'sun', 'moon', 'star', 'cloud', 'rain', 'snow', 'wind', 'mountain',
'river', 'lake', 'sea', 'ocean', 'island', 'bridge', 'road', 'train', 'plane', 'ship', 'bicycle',
- 'circle', 'square', 'garden', 'harp', 'grass', 'forest', 'rock', 'cake', 'pie', 'cookie', 'candy',
- 'butterfly', 'computer', 'phone', 'keyboard', 'mouse', 'cup', 'plate', 'glass', 'door',
- 'window', 'key', 'wallet', 'pillow', 'bed', 'blanket', 'soap', 'towel', 'lamp', 'mirror',
- 'camera', 'hat', 'shirt', 'pants', 'shoes', 'watch', 'ring',
+ 'circle', 'square', 'garden', 'harp', 'grass', 'forest', 'rock', 'cake', 'pie', 'cookie', 'candy',
+ 'butterfly', 'computer', 'phone', 'keyboard', 'mouse', 'cup', 'plate', 'glass', 'door',
+ 'window', 'key', 'wallet', 'pillow', 'bed', 'blanket', 'soap', 'towel', 'lamp', 'mirror',
+ 'camera', 'hat', 'shirt', 'pants', 'shoes', 'watch', 'ring',
'necklace', 'ball', 'toy', 'doll', 'kite', 'balloon', 'guitar', 'violin', 'piano', 'drum',
'trumpet', 'flute', 'viola', 'cello', 'harp', 'banjo', 'tuba',
-]
+];
const words = {
adjectives,
@@ -55,7 +55,7 @@ const words = {
/**
* Select a random item from an array using a random number generator function.
- *
+ *
* @param {Array} arr - The array to select an item from
* @param {function} [random=Math.random] - Random number generator function
* @returns {T} A random item from the array
@@ -73,11 +73,11 @@ const randomItem = (arr, random) => arr[Math.floor((random ?? Math.random)() * a
*
* @example
*
- * let identifier = window.generate_identifier();
+ * let identifier = window.generate_identifier();
* // identifier would be something like 'clever-idea-123'
*
*/
-function generate_identifier(separator = '_', rng = Math.random){
+function generate_identifier (separator = '_', rng = Math.random) {
// return a random combination of first_adj + noun + number (between 0 and 9999)
// e.g. clever-idea-123
return [
@@ -90,9 +90,9 @@ function generate_identifier(separator = '_', rng = Math.random){
// Character set used for generating human-readable, case-insensitive random codes
const HUMAN_READABLE_CASE_INSENSITIVE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
-function generate_random_code(n, {
+function generate_random_code (n, {
rng = Math.random,
- chars = HUMAN_READABLE_CASE_INSENSITIVE
+ chars = HUMAN_READABLE_CASE_INSENSITIVE,
} = {}) {
let code = '';
for ( let i = 0 ; i < n ; i++ ) {
@@ -107,7 +107,7 @@ function generate_random_code(n, {
* @param {number} value - Number to convert to base-36 and append to the right
* @returns {string} Combined uppercase code
*/
-function compose_code(mask, value) {
+function compose_code (mask, value) {
const right_str = value.toString(36);
let out_str = mask;
console.log('right_str', right_str);
@@ -125,4 +125,3 @@ module.exports = {
generate_identifier,
generate_random_code,
};
-
diff --git a/src/backend/src/modules/core/lib/linux.js b/src/backend/src/modules/core/lib/linux.js
index e9e8594805..b8ef87b46d 100644
--- a/src/backend/src/modules/core/lib/linux.js
+++ b/src/backend/src/modules/core/lib/linux.js
@@ -33,9 +33,8 @@ const parse_meminfo = text => {
}
return meminfo;
-}
+};
module.exports = {
parse_meminfo,
};
-
diff --git a/src/backend/src/modules/core/lib/log.js b/src/backend/src/modules/core/lib/log.js
index 0d6b941c36..caa98b8c29 100644
--- a/src/backend/src/modules/core/lib/log.js
+++ b/src/backend/src/modules/core/lib/log.js
@@ -26,7 +26,6 @@ const config = require('../../../config.js');
// (next month) log("tick"); // → "11-01 00:00:01 tick"
// (next year) log("tick"); // → "2026-01-01 00:00:01 tick"
-
/**
* Stringifies a log entry into a formatted string for console output.
* @param {Object} logEntry - The log entry object containing:
@@ -48,7 +47,7 @@ const stringify_log_entry = ({ prefix, log_lvl, crumbs, message, fields, objects
lines.push(m);
m = '';
};
-
+
m = '';
if ( ! config.show_relative_time ) {
@@ -57,7 +56,7 @@ const stringify_log_entry = ({ prefix, log_lvl, crumbs, message, fields, objects
m += prefix ? `${prefix} ` : '';
let levelLabelShown = false;
- if ( log_lvl.label !== 'INFO' || ! config.log_hide_info_label ) {
+ if ( log_lvl.label !== 'INFO' || !config.log_hide_info_label ) {
levelLabelShown = true;
m += `\x1B[${log_lvl.esc}m[${log_lvl.label}\x1B[0m`;
} else {
@@ -98,7 +97,7 @@ const stringify_log_entry = ({ prefix, log_lvl, crumbs, message, fields, objects
let v; try {
v = colorize(JSON.stringify(fields[k]));
} catch (e) {
- v = '' + fields[k];
+ v = `${ fields[k]}`;
}
m += ` \x1B[1m${k}:\x1B[0m ${v}`;
lf();
diff --git a/src/backend/src/modules/core/lib/stdio.js b/src/backend/src/modules/core/lib/stdio.js
index ca7b912838..07f4552b6d 100644
--- a/src/backend/src/modules/core/lib/stdio.js
+++ b/src/backend/src/modules/core/lib/stdio.js
@@ -41,30 +41,28 @@ const split_lines = (str) => {
const lines = [];
let line = '';
let line_length = 0;
- for (const c of str) {
+ for ( const c of str ) {
line += c;
- if (c === '\n') {
+ if ( c === '\n' ) {
lines.push(line);
line = '';
line_length = 0;
} else {
line_length++;
- if (line_length >= process.stdout.columns) {
+ if ( line_length >= process.stdout.columns ) {
lines.push(line);
line = '';
line_length = 0;
}
}
}
- if (line.length) {
+ if ( line.length ) {
lines.push(line);
}
return lines;
};
-
module.exports = {
visible_length,
split_lines,
};
-
diff --git a/src/backend/src/modules/development/DevelopmentModule.js b/src/backend/src/modules/development/DevelopmentModule.js
index 409f9dea39..bff84ded0f 100644
--- a/src/backend/src/modules/development/DevelopmentModule.js
+++ b/src/backend/src/modules/development/DevelopmentModule.js
@@ -1,27 +1,27 @@
/*
* 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 .
*/
-const { AdvancedBase } = require("@heyputer/putility");
+const { AdvancedBase } = require('@heyputer/putility');
/**
* Enable this module when you want performance monitoring.
- *
+ *
* Performance monitoring requires additional setup. Jaegar should be installed
* and running.
*/
@@ -29,7 +29,7 @@ class DevelopmentModule extends AdvancedBase {
async install (context) {
const services = context.get('services');
- const LocalTerminalService = require("./LocalTerminalService");
+ const LocalTerminalService = require('./LocalTerminalService');
services.registerService('local-terminal', LocalTerminalService);
}
}
diff --git a/src/backend/src/modules/development/LocalTerminalService.js b/src/backend/src/modules/development/LocalTerminalService.js
index 205ced9bcb..111d4696a0 100644
--- a/src/backend/src/modules/development/LocalTerminalService.js
+++ b/src/backend/src/modules/development/LocalTerminalService.js
@@ -17,18 +17,17 @@
* along with this program. If not, see .
*/
-const { spawn } = require("child_process");
-const APIError = require("../../api/APIError");
-const configurable_auth = require("../../middleware/configurable_auth");
-const { Endpoint } = require("../../util/expressutil");
-
+const { spawn } = require('child_process');
+const APIError = require('../../api/APIError');
+const configurable_auth = require('../../middleware/configurable_auth');
+const { Endpoint } = require('../../util/expressutil');
const PERM_LOCAL_TERMINAL = 'local-terminal:access';
const path_ = require('path');
-const { Actor } = require("../../services/auth/Actor");
-const BaseService = require("../../services/BaseService");
-const { Context } = require("../../util/context");
+const { Actor } = require('../../services/auth/Actor');
+const BaseService = require('../../services/BaseService');
+const { Context } = require('../../util/context');
class LocalTerminalService extends BaseService {
_construct () {
@@ -37,15 +36,13 @@ class LocalTerminalService extends BaseService {
get_profiles () {
return {
['api-test']: {
- cwd: path_.join(
- __dirname,
- '../../../../../',
- 'tools/api-tester',
- ),
+ cwd: path_.join(__dirname,
+ '../../../../../',
+ 'tools/api-tester'),
shell: [
'/usr/bin/env', 'node',
'apitest.js',
- '--config=config.yml'
+ '--config=config.yml',
],
allow_args: true,
},
@@ -55,7 +52,7 @@ class LocalTerminalService extends BaseService {
const r_group = (() => {
const require = this.require;
const express = require('express');
- return express.Router()
+ return express.Router();
})();
app.use('/local-terminal', r_group);
@@ -70,7 +67,7 @@ class LocalTerminalService extends BaseService {
const actor = Context.get('actor');
const can_access = actor &&
await svc_permission.check(actor, PERM_LOCAL_TERMINAL);
-
+
if ( ! can_access ) {
throw APIError.create('permission_denied', null, {
permission: PERM_LOCAL_TERMINAL,
@@ -107,41 +104,35 @@ class LocalTerminalService extends BaseService {
proc.stdout.on('data', data => {
const base64 = data.toString('base64');
console.log('---------------------- CHUNK?', base64);
- svc_socketio.send(
- { room: req.user.id },
- 'local-terminal.stdout',
- {
- term_uuid,
- base64,
- },
- );
+ svc_socketio.send({ room: req.user.id },
+ 'local-terminal.stdout',
+ {
+ term_uuid,
+ base64,
+ });
});
proc.stderr.on('data', data => {
const base64 = data.toString('base64');
console.log('---------------------- CHUNK?', base64);
- svc_socketio.send(
- { room: req.user.id },
- 'local-terminal.stderr',
- {
- term_uuid,
- base64,
- },
- );
+ svc_socketio.send({ room: req.user.id },
+ 'local-terminal.stderr',
+ {
+ term_uuid,
+ base64,
+ });
});
}
-
+
proc.on('exit', () => {
this.log.noticeme(`[${term_uuid}] Process exited (${proc.exitCode})`);
delete this.sessions_[term_uuid];
const svc_socketio = req.services.get('socketio');
- svc_socketio.send(
- { room: req.user.id },
- 'local-terminal.exit',
- {
- term_uuid,
- },
- );
+ svc_socketio.send({ room: req.user.id },
+ 'local-terminal.exit',
+ {
+ term_uuid,
+ });
});
this.sessions_[term_uuid] = {
@@ -178,7 +169,7 @@ class LocalTerminalService extends BaseService {
const base64 = Buffer.from(msg.data, 'base64');
session.proc.stdin.write(base64);
- })
+ });
});
}
}
diff --git a/src/backend/src/modules/dns/DNSModule.js b/src/backend/src/modules/dns/DNSModule.js
index 45abb606aa..7049a3e254 100644
--- a/src/backend/src/modules/dns/DNSModule.js
+++ b/src/backend/src/modules/dns/DNSModule.js
@@ -1,9 +1,9 @@
-const { AdvancedBase } = require("@heyputer/putility");
+const { AdvancedBase } = require('@heyputer/putility');
class DNSModule extends AdvancedBase {
async install (context) {
const services = context.get('services');
-
+
const { DNSService } = require('./DNSService');
services.registerService('dns', DNSService);
}
diff --git a/src/backend/src/modules/dns/DNSService.js b/src/backend/src/modules/dns/DNSService.js
index 6c75fc2ff1..8505c41bfa 100644
--- a/src/backend/src/modules/dns/DNSService.js
+++ b/src/backend/src/modules/dns/DNSService.js
@@ -1,5 +1,5 @@
-const BaseService = require("../../services/BaseService");
-const { sleep } = require("../../util/asyncutil");
+const BaseService = require('../../services/BaseService');
+const { sleep } = require('../../util/asyncutil');
/**
* DNS service that provides DNS client functionality and optional test server
@@ -17,12 +17,12 @@ class DNSService extends BaseService {
nameServers: ['127.0.0.1'],
port: 5300,
});
-
+
if ( this.config.test_server ) {
this.test_server_();
}
}
-
+
/**
* Returns the DNS client instance
* @returns {Object} The DNS client
@@ -30,22 +30,22 @@ class DNSService extends BaseService {
get_client () {
return this.dns;
}
-
+
/**
* Creates and starts a test DNS server that responds to A and TXT record queries
* The server listens on port 5300 and returns mock responses for testing purposes
*/
test_server_ () {
const dns2 = require('dns2');
- const { Packet } = dns2
-
+ const { Packet } = dns2;
+
const server = dns2.createServer({
udp: true,
handle: (request, send, rinfo) => {
const { questions } = request;
const response = Packet.createResponseFromRequest(request);
- for (const question of questions) {
- if (question.type === Packet.TYPE.A || question.type === Packet.TYPE.ANY) {
+ for ( const question of questions ) {
+ if ( question.type === Packet.TYPE.A || question.type === Packet.TYPE.ANY ) {
response.answers.push({
name: question.name,
type: Packet.TYPE.A,
@@ -55,45 +55,47 @@ class DNSService extends BaseService {
});
}
- if (question.type === Packet.TYPE.TXT || question.type === Packet.TYPE.ANY) {
+ if ( question.type === Packet.TYPE.TXT || question.type === Packet.TYPE.ANY ) {
response.answers.push({
name: question.name,
type: Packet.TYPE.TXT,
class: Packet.CLASS.IN,
ttl: 300,
data: [
- JSON.stringify({ username: 'ed3' })
+ JSON.stringify({ username: 'ed3' }),
],
});
}
}
send(response);
- }
+ },
});
server.on('listening', () => {
this.log.debug('Fake DNS server listening', server.addresses());
-
- if ( this.config.test_server_selftest ) (async () => {
- await sleep(5000);
- {
- console.log('Trying first test')
- const result = await this.dns.resolveA('test.local');
- console.log('Test 1', result);
- }
- {
- console.log('Trying second test')
- const result = await this.dns.resolve(`_puter-verify.test.local`, 'TXT');
- console.log('Test 2', result);
- }
- })();
+
+ if ( this.config.test_server_selftest ) {
+ (async () => {
+ await sleep(5000);
+ {
+ console.log('Trying first test');
+ const result = await this.dns.resolveA('test.local');
+ console.log('Test 1', result);
+ }
+ {
+ console.log('Trying second test');
+ const result = await this.dns.resolve('_puter-verify.test.local', 'TXT');
+ console.log('Test 2', result);
+ }
+ })();
+ }
});
-
+
server.on('close', () => {
console.log('Fake DNS server closed');
this.log.noticeme('Fake DNS server closed');
- })
-
+ });
+
server.on('request', (request, response, rinfo) => {
console.log(request.header.id, request.questions[0]);
});
@@ -102,11 +104,10 @@ class DNSService extends BaseService {
console.log('Client sent an invalid request', error);
});
-
server.listen({
udp: {
port: 5300,
- address: "127.0.0.1",
+ address: '127.0.0.1',
},
});
}
diff --git a/src/backend/src/modules/domain/DomainModule.js b/src/backend/src/modules/domain/DomainModule.js
index 8713b80b8b..c592f328b2 100644
--- a/src/backend/src/modules/domain/DomainModule.js
+++ b/src/backend/src/modules/domain/DomainModule.js
@@ -1,4 +1,4 @@
-const { AdvancedBase } = require("@heyputer/putility");
+const { AdvancedBase } = require('@heyputer/putility');
class DomainModule extends AdvancedBase {
async install (context) {
diff --git a/src/backend/src/modules/domain/DomainVerificationService.js b/src/backend/src/modules/domain/DomainVerificationService.js
index 5668480c42..f8c76ad96f 100644
--- a/src/backend/src/modules/domain/DomainVerificationService.js
+++ b/src/backend/src/modules/domain/DomainVerificationService.js
@@ -1,5 +1,5 @@
-const { get_user } = require("../../helpers");
-const BaseService = require("../../services/BaseService");
+const { get_user } = require('../../helpers');
+const BaseService = require('../../services/BaseService');
class DomainVerificationService extends BaseService {
_init () {
@@ -7,7 +7,7 @@ class DomainVerificationService extends BaseService {
}
async get_controlling_user ({ domain }) {
const svc_event = this.services.get('event');
-
+
// 1 :: Allow event listeners to verify domains
const event = {
domain,
@@ -17,7 +17,7 @@ class DomainVerificationService extends BaseService {
if ( event.user ) {
return event.user;
}
-
+
// 2 :: If there is no controlling user, 'admin' is the
// controlling user.
return await get_user({ username: 'admin' });
@@ -32,8 +32,8 @@ class DomainVerificationService extends BaseService {
handler: async (args, log) => {
const res = await this.get_controlling_user({ domain: args[0] });
log.log(res);
- }
- }
+ },
+ },
]);
}
}
diff --git a/src/backend/src/modules/domain/TXTVerifyService.js b/src/backend/src/modules/domain/TXTVerifyService.js
index 7b535e0436..33cebe08be 100644
--- a/src/backend/src/modules/domain/TXTVerifyService.js
+++ b/src/backend/src/modules/domain/TXTVerifyService.js
@@ -1,6 +1,6 @@
-const { get_user } = require("../../helpers");
-const BaseService = require("../../services/BaseService");
-const { atimeout } = require("../../util/asyncutil");
+const { get_user } = require('../../helpers');
+const BaseService = require('../../services/BaseService');
+const { atimeout } = require('../../util/asyncutil');
class TXTVerifyService extends BaseService {
['__on_boot.consolidation'] () {
@@ -11,26 +11,22 @@ class TXTVerifyService extends BaseService {
svc_event.on('domain.get-controlling-user', async (_, event) => {
const record_name = `_puter-verify.${event.domain}`;
try {
- const result = await atimeout(
- 5000,
- dns.resolve(record_name, 'TXT'),
- );
-
- const answer = result.answers.filter(
- a => a.name === record_name &&
- a.type === 16
- )[0];
-
+ const result = await atimeout(5000,
+ dns.resolve(record_name, 'TXT'));
+
+ const answer = result.answers.filter(a => a.name === record_name &&
+ a.type === 16)[0];
+
const data_raw = answer.data;
const data = JSON.parse(data_raw);
event.user = await get_user({ username: data.username });
} catch (e) {
console.error('ERROR', e);
}
- })
+ });
}
}
module.exports = {
TXTVerifyService,
-}
+};
diff --git a/src/backend/src/modules/entitystore/EntityStoreInterfaceService.js b/src/backend/src/modules/entitystore/EntityStoreInterfaceService.js
index 53bd6a9ecf..5b6e7ff294 100644
--- a/src/backend/src/modules/entitystore/EntityStoreInterfaceService.js
+++ b/src/backend/src/modules/entitystore/EntityStoreInterfaceService.js
@@ -1,24 +1,24 @@
/*
* Copyright (C) 2025-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 .
*/
// METADATA // {"ai-commented":{"service":"claude"}}
-const BaseService = require("../../services/BaseService");
+const BaseService = require('../../services/BaseService');
/**
* Service class that manages Entity Store interface registrations.
@@ -34,7 +34,7 @@ class EntityStoreInterfaceService extends BaseService {
async ['__on_driver.register.interfaces'] () {
const svc_registry = this.services.get('registry');
const col_interfaces = svc_registry.get('interfaces');
-
+
// Define the standard CRUD interface methods that will be reused
const crudMethods = {
create: {
@@ -45,14 +45,14 @@ class EntityStoreInterfaceService extends BaseService {
required: true,
},
options: { type: 'json' },
- }
+ },
},
read: {
parameters: {
uid: { type: 'string' },
id: { type: 'json' },
params: { type: 'json' },
- }
+ },
},
select: {
parameters: {
@@ -60,7 +60,7 @@ class EntityStoreInterfaceService extends BaseService {
offset: { type: 'number' },
limit: { type: 'number' },
params: { type: 'json' },
- }
+ },
},
update: {
parameters: {
@@ -71,7 +71,7 @@ class EntityStoreInterfaceService extends BaseService {
required: true,
},
options: { type: 'json' },
- }
+ },
},
upsert: {
parameters: {
@@ -82,47 +82,47 @@ class EntityStoreInterfaceService extends BaseService {
required: true,
},
options: { type: 'json' },
- }
+ },
},
delete: {
parameters: {
uid: { type: 'string' },
id: { type: 'json' },
- }
+ },
},
};
-
+
// Register the crud-q interface
col_interfaces.set('crud-q', {
- methods: { ...crudMethods }
+ methods: { ...crudMethods },
});
// Register entity-specific interfaces that use crud-q
const entityInterfaces = [
{
name: 'puter-apps',
- description: 'Manage a developer\'s apps on Puter.'
+ description: 'Manage a developer\'s apps on Puter.',
},
{
name: 'puter-subdomains',
- description: 'Manage subdomains on Puter.'
+ description: 'Manage subdomains on Puter.',
},
{
name: 'puter-notifications',
- description: 'Read notifications on Puter.'
- }
+ description: 'Read notifications on Puter.',
+ },
];
// Register each entity interface with the same CRUD methods
- for (const entity of entityInterfaces) {
+ for ( const entity of entityInterfaces ) {
col_interfaces.set(entity.name, {
description: entity.description,
- methods: { ...crudMethods }
+ methods: { ...crudMethods },
});
}
}
}
module.exports = {
- EntityStoreInterfaceService
+ EntityStoreInterfaceService,
};
\ No newline at end of file
diff --git a/src/backend/src/modules/entitystore/EntityStoreModule.js b/src/backend/src/modules/entitystore/EntityStoreModule.js
index fa42c8789e..afad23600b 100644
--- a/src/backend/src/modules/entitystore/EntityStoreModule.js
+++ b/src/backend/src/modules/entitystore/EntityStoreModule.js
@@ -1,32 +1,32 @@
/*
* Copyright (C) 2025-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 .
*/
-const { AdvancedBase } = require("@heyputer/putility");
-const { EntityStoreInterfaceService } = require("./EntityStoreInterfaceService");
+const { AdvancedBase } = require('@heyputer/putility');
+const { EntityStoreInterfaceService } = require('./EntityStoreInterfaceService');
/**
* A module for registering entity store interfaces.
*/
class EntityStoreModule extends AdvancedBase {
- async install(context) {
+ async install (context) {
const services = context.get('services');
-
+
// Register interface services
services.registerService('entitystore-interface', EntityStoreInterfaceService);
}
diff --git a/src/backend/src/modules/hostos/HostOSModule.js b/src/backend/src/modules/hostos/HostOSModule.js
index c7e50439c2..0ee2e11369 100644
--- a/src/backend/src/modules/hostos/HostOSModule.js
+++ b/src/backend/src/modules/hostos/HostOSModule.js
@@ -1,4 +1,4 @@
-const { AdvancedBase } = require("@heyputer/putility");
+const { AdvancedBase } = require('@heyputer/putility');
class HostOSModule extends AdvancedBase {
async install (context) {
diff --git a/src/backend/src/modules/hostos/ProcessService.js b/src/backend/src/modules/hostos/ProcessService.js
index d96c33ac2b..1a4deb70ef 100644
--- a/src/backend/src/modules/hostos/ProcessService.js
+++ b/src/backend/src/modules/hostos/ProcessService.js
@@ -1,4 +1,4 @@
-const BaseService = require("../../services/BaseService");
+const BaseService = require('../../services/BaseService');
class ProxyLogger {
constructor (log) {
@@ -9,7 +9,7 @@ class ProxyLogger {
stream.on('data', (chunk) => {
buffer += chunk.toString();
let lineEndIndex = buffer.indexOf('\n');
- while (lineEndIndex !== -1) {
+ while ( lineEndIndex !== -1 ) {
const line = buffer.substring(0, lineEndIndex);
this.log(line);
buffer = buffer.substring(lineEndIndex + 1);
@@ -18,7 +18,7 @@ class ProxyLogger {
});
stream.on('end', () => {
- if (buffer.length) {
+ if ( buffer.length ) {
this.log(buffer);
}
});
@@ -42,15 +42,15 @@ class ProcessService extends BaseService {
process.on('exit', () => {
this.exit_all_();
- })
+ });
}
log_ (name, isErr, line) {
let txt = `[${name}:`;
txt += isErr
- ? `\x1B[34;1m2\x1B[0m`
- : `\x1B[32;1m1\x1B[0m`;
- txt += '] ' + line;
+ ? '\x1B[34;1m2\x1B[0m'
+ : '\x1B[32;1m1\x1B[0m';
+ txt += `] ${ line}`;
this.log.info(txt);
}
@@ -66,13 +66,11 @@ class ProcessService extends BaseService {
for ( const k in env_processed ) {
if ( typeof env_processed[k] !== 'function' ) continue;
env_processed[k] = env_processed[k]({
- global_config: this.global_config
+ global_config: this.global_config,
});
}
- this.log.debug(
- 'command',
- { command, args },
- );
+ this.log.debug('command',
+ { command, args });
const proc = this.modules.spawn(command, args, {
shell: true,
env: {
@@ -91,7 +89,7 @@ class ProcessService extends BaseService {
proc.on('exit', () => {
this.log.info(`[${name}:exit] Process exited (${proc.exitCode})`);
this.instances = this.instances.filter((inst) => inst.proc !== proc);
- })
+ });
}
}
diff --git a/src/backend/src/modules/internet/InternetModule.js b/src/backend/src/modules/internet/InternetModule.js
index fc07a145bc..84a13af55d 100644
--- a/src/backend/src/modules/internet/InternetModule.js
+++ b/src/backend/src/modules/internet/InternetModule.js
@@ -1,11 +1,11 @@
-const { AdvancedBase } = require("@heyputer/putility");
-const config = require("../../config.js");
+const { AdvancedBase } = require('@heyputer/putility');
+const config = require('../../config.js');
class InternetModule extends AdvancedBase {
async install (context) {
const services = context.get('services');
- if ( !! config?.services?.['wisp-relay'] ) {
+ if ( config?.services?.['wisp-relay'] ) {
const WispRelayService = require('./WispRelayService.js');
services.registerService('wisp-relay', WispRelayService);
}
diff --git a/src/backend/src/modules/internet/WispRelayService.js b/src/backend/src/modules/internet/WispRelayService.js
index c781668d48..95f012f7a4 100644
--- a/src/backend/src/modules/internet/WispRelayService.js
+++ b/src/backend/src/modules/internet/WispRelayService.js
@@ -1,4 +1,4 @@
-const BaseService = require("../../services/BaseService");
+const BaseService = require('../../services/BaseService');
class WispRelayService extends BaseService {
_init () {
diff --git a/src/backend/src/modules/kvstore/KVStoreInterfaceService.js b/src/backend/src/modules/kvstore/KVStoreInterfaceService.js
index a902052e51..12cfae9a96 100644
--- a/src/backend/src/modules/kvstore/KVStoreInterfaceService.js
+++ b/src/backend/src/modules/kvstore/KVStoreInterfaceService.js
@@ -64,7 +64,7 @@ class KVStoreInterfaceService extends BaseService {
* Service class for managing KVStore interface registrations.
* Extends the base service to provide key-value store interface management.
*/
- async ['__on_driver.register.interfaces']() {
+ async ['__on_driver.register.interfaces'] () {
const svc_registry = this.services.get('registry');
const col_interfaces = svc_registry.get('interfaces');
diff --git a/src/backend/src/modules/kvstore/KVStoreModule.js b/src/backend/src/modules/kvstore/KVStoreModule.js
index 55dd597092..101e7c000a 100644
--- a/src/backend/src/modules/kvstore/KVStoreModule.js
+++ b/src/backend/src/modules/kvstore/KVStoreModule.js
@@ -1,32 +1,32 @@
/*
* Copyright (C) 2025-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 .
*/
-const { AdvancedBase } = require("@heyputer/putility");
-const { KVStoreInterfaceService } = require("./KVStoreInterfaceService");
+const { AdvancedBase } = require('@heyputer/putility');
+const { KVStoreInterfaceService } = require('./KVStoreInterfaceService');
/**
* A module for registering key-value store interfaces.
*/
class KVStoreModule extends AdvancedBase {
- async install(context) {
+ async install (context) {
const services = context.get('services');
-
+
// Register interface services
services.registerService('kvstore-interface', KVStoreInterfaceService);
}
diff --git a/src/backend/src/modules/perfmon/PerfMonModule.js b/src/backend/src/modules/perfmon/PerfMonModule.js
index f27e38ff62..c76d7a365e 100644
--- a/src/backend/src/modules/perfmon/PerfMonModule.js
+++ b/src/backend/src/modules/perfmon/PerfMonModule.js
@@ -1,27 +1,27 @@
/*
* 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 .
*/
-const { AdvancedBase } = require("@heyputer/putility");
+const { AdvancedBase } = require('@heyputer/putility');
/**
* Enable this module when you want performance monitoring.
- *
+ *
* Performance monitoring requires additional setup. Jaegar should be installed
* and running.
*/
@@ -29,7 +29,7 @@ class PerfMonModule extends AdvancedBase {
async install (context) {
const services = context.get('services');
- const TelemetryService = require("./TelemetryService");
+ const TelemetryService = require('./TelemetryService');
services.registerService('telemetry', TelemetryService);
}
}
diff --git a/src/backend/src/modules/perfmon/TelemetryService.js b/src/backend/src/modules/perfmon/TelemetryService.js
index 4444044bde..7d32b46b60 100644
--- a/src/backend/src/modules/perfmon/TelemetryService.js
+++ b/src/backend/src/modules/perfmon/TelemetryService.js
@@ -16,15 +16,15 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-const opentelemetry = require("@opentelemetry/api");
+const opentelemetry = require('@opentelemetry/api');
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { PeriodicExportingMetricReader, ConsoleMetricExporter } = require('@opentelemetry/sdk-metrics');
-const { Resource } = require("@opentelemetry/resources");
-const { SemanticResourceAttributes } = require("@opentelemetry/semantic-conventions");
-const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");
-const { ConsoleSpanExporter, BatchSpanProcessor } = require("@opentelemetry/sdk-trace-base");
+const { Resource } = require('@opentelemetry/resources');
+const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
+const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
+const { ConsoleSpanExporter, BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const config = require('../../config');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
@@ -33,13 +33,12 @@ const BaseService = require('../../services/BaseService');
class TelemetryService extends BaseService {
_construct () {
const resource = Resource.default().merge(
- new Resource({
- [SemanticResourceAttributes.SERVICE_NAME]: "puter-backend",
- [SemanticResourceAttributes.SERVICE_VERSION]: "0.1.0"
- }),
- );
+ new Resource({
+ [SemanticResourceAttributes.SERVICE_NAME]: 'puter-backend',
+ [SemanticResourceAttributes.SERVICE_VERSION]: '0.1.0',
+ }));
- const provider = new NodeTracerProvider({ resource })
+ const provider = new NodeTracerProvider({ resource });
const exporter = this.getConfiguredExporter_();
this.exporter = exporter;
@@ -51,20 +50,18 @@ class TelemetryService extends BaseService {
const sdk = new NodeSDK({
traceExporter: new ConsoleSpanExporter(),
metricReader: new PeriodicExportingMetricReader({
- exporter: new ConsoleMetricExporter()
+ exporter: new ConsoleMetricExporter(),
}),
- instrumentations: [getNodeAutoInstrumentations()]
+ instrumentations: [getNodeAutoInstrumentations()],
});
this.sdk = sdk;
this.sdk.start();
- this.tracer_ = opentelemetry.trace.getTracer(
- 'puter-tracer'
- );
+ this.tracer_ = opentelemetry.trace.getTracer('puter-tracer');
}
-
+
_init () {
const svc_context = this.services.get('context');
svc_context.register_context_hook('pre_arun', ({ hints, trace_name, callback, replace_callback }) => {
@@ -75,7 +72,7 @@ class TelemetryService extends BaseService {
return await this.tracer_.startActiveSpan(trace_name, async span => {
try {
return await callback();
- } catch (error) {
+ } catch ( error ) {
span.setStatus({ code: opentelemetry.SpanStatusCode.ERROR, message: error.message });
throw error;
} finally {
@@ -86,7 +83,7 @@ class TelemetryService extends BaseService {
});
}
- getConfiguredExporter_() {
+ getConfiguredExporter_ () {
if ( config.jaeger ?? this.config.jaeger ) {
return new OTLPTraceExporter(config.jaeger ?? this.config.jaeger);
}
diff --git a/src/backend/src/modules/puterai/AIInterfaceService.js b/src/backend/src/modules/puterai/AIInterfaceService.js
index a8804d9508..eed23a7ea4 100644
--- a/src/backend/src/modules/puterai/AIInterfaceService.js
+++ b/src/backend/src/modules/puterai/AIInterfaceService.js
@@ -1,25 +1,24 @@
/*
* 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 .
*/
// METADATA // {"ai-commented":{"service":"claude"}}
-const BaseService = require("../../services/BaseService");
-
+const BaseService = require('../../services/BaseService');
/**
* Service class that manages AI interface registrations and configurations.
@@ -37,7 +36,7 @@ class AIInterfaceService extends BaseService {
async ['__on_driver.register.interfaces'] () {
const svc_registry = this.services.get('registry');
const col_interfaces = svc_registry.get('interfaces');
-
+
col_interfaces.set('puter-ocr', {
description: 'Optical character recognition',
methods: {
@@ -81,10 +80,10 @@ class AIInterfaceService extends BaseService {
type: {
$: 'stream',
content_type: 'image',
- }
+ },
},
},
- }
+ },
});
col_interfaces.set('puter-chat-completion', {
@@ -113,8 +112,8 @@ class AIInterfaceService extends BaseService {
max_tokens: { type: 'number' },
},
result: { type: 'json' },
- }
- }
+ },
+ },
});
col_interfaces.set('puter-image-generation', {
@@ -150,22 +149,22 @@ class AIInterfaceService extends BaseService {
type: {
$: 'stream',
content_type: 'image',
- }
+ },
},
{
names: ['url'],
type: {
$: 'string:url:web',
content_type: 'image',
- }
+ },
},
],
result: {
description: 'URL of the generated image.',
- type: 'string'
- }
- }
- }
+ type: 'string',
+ },
+ },
+ },
});
col_interfaces.set('puter-video-generation', {
@@ -200,22 +199,22 @@ class AIInterfaceService extends BaseService {
type: {
$: 'string:url:web',
content_type: 'video',
- }
+ },
},
{
names: ['video'],
type: {
$: 'stream',
content_type: 'video',
- }
+ },
},
],
result: {
description: 'Video asset descriptor or URL for the generated video.',
- type: 'json'
- }
- }
- }
+ type: 'json',
+ },
+ },
+ },
});
col_interfaces.set('puter-tts', {
@@ -254,12 +253,12 @@ class AIInterfaceService extends BaseService {
type: {
$: 'stream',
content_type: 'audio',
- }
+ },
},
- ]
+ ],
},
- }
- })
+ },
+ });
col_interfaces.set('puter-speech2txt', {
description: 'Speech to text transcription and translation.',
@@ -308,5 +307,5 @@ class AIInterfaceService extends BaseService {
}
module.exports = {
- AIInterfaceService
+ AIInterfaceService,
};
diff --git a/src/backend/src/modules/puterai/AITestModeService.js b/src/backend/src/modules/puterai/AITestModeService.js
index 04ed6d1331..60aee8f5a1 100644
--- a/src/backend/src/modules/puterai/AITestModeService.js
+++ b/src/backend/src/modules/puterai/AITestModeService.js
@@ -1,25 +1,24 @@
/*
* 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 .
*/
// METADATA // {"ai-commented":{"service":"claude"}}
-const BaseService = require("../../services/BaseService");
-
+const BaseService = require('../../services/BaseService');
/**
* Service class that handles AI test mode functionality.
diff --git a/src/backend/src/modules/puterai/AWSPollyService.js b/src/backend/src/modules/puterai/AWSPollyService.js
index bf3d41a1b7..bb86e990bc 100644
--- a/src/backend/src/modules/puterai/AWSPollyService.js
+++ b/src/backend/src/modules/puterai/AWSPollyService.js
@@ -26,10 +26,10 @@ const { Context } = require('../../util/context');
// Polly price calculation per engine
const ENGINE_PRICING = {
- 'standard': 400, // $4.00 per 1M characters
- 'neural': 1600, // $16.00 per 1M characters
- 'long-form': 10000, // $100.00 per 1M characters
- 'generative': 3000, // $30.00 per 1M characters
+ 'standard': 400, // $4.00 per 1M characters
+ 'neural': 1600, // $16.00 per 1M characters
+ 'long-form': 10000, // $100.00 per 1M characters
+ 'generative': 3000, // $30.00 per 1M characters
};
// Valid engine types
@@ -46,7 +46,7 @@ const VALID_ENGINES = ['standard', 'neural', 'long-form', 'generative'];
class AWSPollyService extends BaseService {
/** @type {import('../../services/MeteringService/MeteringService').MeteringService} */
- get meteringService() {
+ get meteringService () {
return this.services.get('meteringService').meteringService;
}
@@ -60,13 +60,13 @@ class AWSPollyService extends BaseService {
* the internal state needed for AWS Polly client management.
* @returns {Promise}
*/
- async _construct() {
+ async _construct () {
this.clients_ = {};
}
static IMPLEMENTS = {
['driver-capabilities']: {
- supports_test_mode(iface, method_name) {
+ supports_test_mode (iface, method_name) {
return iface === 'puter-tts' && method_name === 'synthesize';
},
},
@@ -79,7 +79,7 @@ class AWSPollyService extends BaseService {
* @property {Object} synthesize - Converts text to speech using specified voice/language
* @property {Function} supports_test_mode - Indicates test mode support for methods
*/
- async list_voices({ engine } = {}) {
+ async list_voices ({ engine } = {}) {
const polly_voices = await this.describe_voices();
let voices = polly_voices.Voices;
@@ -104,14 +104,14 @@ class AWSPollyService extends BaseService {
return voices;
},
- async list_engines() {
+ async list_engines () {
return VALID_ENGINES.map(engine => ({
id: engine,
name: engine.charAt(0).toUpperCase() + engine.slice(1),
pricing_per_million_chars: ENGINE_PRICING[engine] / 100, // Convert microcents to dollars
}));
},
- async synthesize({
+ async synthesize ({
text, voice,
ssml, language,
engine = 'standard',
@@ -126,7 +126,7 @@ class AWSPollyService extends BaseService {
}
// Validate engine
- if ( !VALID_ENGINES.includes(engine) ) {
+ if ( ! VALID_ENGINES.includes(engine) ) {
throw APIError.create('invalid_engine', null, { engine, valid_engines: VALID_ENGINES });
}
@@ -166,14 +166,14 @@ class AWSPollyService extends BaseService {
* @private
* @returns {Object} Object containing AWS access key ID and secret access key
*/
- _create_aws_credentials() {
+ _create_aws_credentials () {
return {
accessKeyId: this.config.aws.access_key,
secretAccessKey: this.config.aws.secret_key,
};
}
- _get_client(region) {
+ _get_client (region) {
if ( ! region ) {
region = this.config.aws?.region ?? this.global_config.aws?.region
?? 'us-west-2';
@@ -194,7 +194,7 @@ class AWSPollyService extends BaseService {
* @description Fetches voice information from AWS Polly API and caches it for 10 minutes
* Uses KV store for caching to avoid repeated API calls
*/
- async describe_voices() {
+ async describe_voices () {
let voices = this.modules.kv.get('svc:polly:voices');
if ( voices ) {
this.log.debug('voices cache hit');
@@ -228,12 +228,12 @@ class AWSPollyService extends BaseService {
* @param {string} [options.engine] - TTS engine to use ('standard', 'neural', 'long-form', 'generative')
* @returns {Promise} The synthesized speech response
*/
- async synthesize_speech(text, { format, voice_id, language, text_type, engine = 'standard' }) {
+ async synthesize_speech (text, { format, voice_id, language, text_type, engine = 'standard' }) {
const client = this._get_client(this.config.aws.region);
let voice = voice_id ?? undefined;
- if ( ! voice && language ) {
+ if ( !voice && language ) {
this.log.debug('getting language appropriate voice', { language, engine });
voice = await this.maybe_get_language_appropriate_voice_(language, engine);
}
@@ -268,7 +268,7 @@ class AWSPollyService extends BaseService {
* @returns {Promise} The voice ID if found, null if no matching voice exists
* @private
*/
- async maybe_get_language_appropriate_voice_(language, engine = 'standard') {
+ async maybe_get_language_appropriate_voice_ (language, engine = 'standard') {
const voices = await this.describe_voices();
const voice = voices.Voices.find((voice) => {
@@ -288,7 +288,7 @@ class AWSPollyService extends BaseService {
* @returns {Promise} The default voice ID for the engine
* @private
*/
- async get_default_voice_for_engine_(engine = 'standard') {
+ async get_default_voice_for_engine_ (engine = 'standard') {
const voices = await this.describe_voices();
// Common default voices for each engine
diff --git a/src/backend/src/modules/puterai/AWSTextractService.js b/src/backend/src/modules/puterai/AWSTextractService.js
index ddfc32a6b8..e67783513f 100644
--- a/src/backend/src/modules/puterai/AWSTextractService.js
+++ b/src/backend/src/modules/puterai/AWSTextractService.js
@@ -32,7 +32,7 @@ const { Context } = require('../../util/context');
*/
class AWSTextractService extends BaseService {
/** @type {import('../../services/MeteringService/MeteringService').MeteringService} */
- get meteringService(){
+ get meteringService () {
return this.services.get('meteringService').meteringService;
}
/**
@@ -41,13 +41,13 @@ class AWSTextractService extends BaseService {
* Implements interfaces for OCR recognition and driver capabilities
* @extends BaseService
*/
- _construct() {
+ _construct () {
this.clients_ = {};
}
static IMPLEMENTS = {
['driver-capabilities']: {
- supports_test_mode(iface, method_name) {
+ supports_test_mode (iface, method_name) {
return iface === 'puter-ocr' && method_name === 'recognize';
},
},
@@ -59,7 +59,7 @@ class AWSTextractService extends BaseService {
* @param {boolean} params.test_mode - If true, returns sample test output instead of processing
* @returns {Promise
`;
- return html;
+ html += ``;
+ html += '';
+ if ( options.jsfiles && options.jsfiles.length > 0 ) {
+ options.jsfiles.forEach(jsfile => {
+ html += ``;
+ });
+ }
+ html += '';
+ html += '';
+ return html;
}
module.exports = html_footer;
\ No newline at end of file
diff --git a/src/backend/src/html_head.js b/src/backend/src/html_head.js
index ac5b325be5..447bb76995 100644
--- a/src/backend/src/html_head.js
+++ b/src/backend/src/html_head.js
@@ -16,24 +16,24 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-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 += ``;
- html += ``;
- html += `
`;
- // meta tags
- html += ``;
- html += ``;
- html += ``;
- html += ``;
- // title
- html += `
${encode(options.title ?? 'Puter')}
`;
- // favicons
- html += `
+function html_head (options) {
+ let canonical_url = `${config.origin}/${options.page === 'index' ? '' : options.page}`;
+ let html = '';
+ html += '';
+ html += ``;
+ html += '
';
+ // meta tags
+ html += '';
+ html += '';
+ html += ``;
+ html += '';
+ // title
+ html += `
${encode(options.title ?? 'Puter')}
`;
+ // favicons
+ html += `
@@ -51,58 +51,58 @@ function html_head(options) {
`;
- // Roboto font
- html += ``;
+ // Roboto font
+ html += '';
- // canonical link
- html += ``;
+ // canonical link
+ html += ``;
- // preload images
- if(options.page === 'index'){
- html += ``;
- html += ``;
- }
+ // preload images
+ if ( options.page === 'index' ) {
+ html += '';
+ html += '';
+ }
- // Facebook meta tags
- html += ``;
- html += ``;
- html += ``;
- html += ``;
- html += ``;
+ // Facebook meta tags
+ html += ``;
+ html += '';
+ html += ``;
+ html += ``;
+ html += ``;
- // Twitter meta tags
- html += ``;
- html += ``;
- html += ``;
- html += ``;
- html += ``;
- html += ``;
+ // Twitter meta tags
+ html += '';
+ html += ``;
+ html += ``;
+ html += ``;
+ html += ``;
+ html += ``;
- // CSS
- html += ``;
- html += ``;
+ // CSS
+ html += '';
+ html += '';
- html += ``;
- html += `