Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 102 additions & 62 deletions src/backend/src/Extension.js
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

const { AdvancedBase } = require("@heyputer/putility");
const EmitterFeature = require("@heyputer/putility/src/features/EmitterFeature");
const { Context } = require("./util/context");
const { ExtensionServiceState } = require("./ExtensionService");
const { display_time } = require("@heyputer/putility/src/libs/time");
const { AdvancedBase } = require('@heyputer/putility');
const EmitterFeature = require('@heyputer/putility/src/features/EmitterFeature');
const { Context } = require('./util/context');
const { ExtensionServiceState } = require('./ExtensionService');
const { display_time } = require('@heyputer/putility/src/libs/time');

/**
* This class creates the `extension` global that is seen by Puter backend
Expand All @@ -33,11 +33,11 @@ class Extension extends AdvancedBase {
decorators: [
fn => Context.get(undefined, {
allow_fallback: true,
}).abind(fn)
]
}).abind(fn),
],
}),
];

randomBrightColor() {
// Bright colors in ANSI (foreground codes 90–97)
const brightColors = [
Expand All @@ -52,30 +52,30 @@ class Extension extends AdvancedBase {
return brightColors[Math.floor(Math.random() * brightColors.length)];
}

constructor (...a) {
constructor(...a) {
super(...a);
this.service = null;
this.log = null;
this.ensure_service_();

// this.terminal_color = this.randomBrightColor();
this.terminal_color = 94;

this.log = (...a) => {
this.log_context.info(a.join(' '));
};
this.LOG = (...a) => {
this.log_context.noticeme(a.join(' '));
};
['info','warn','debug','error','tick','noticeme','system'].forEach(lvl => {
['info', 'warn', 'debug', 'error', 'tick', 'noticeme', 'system'].forEach(lvl => {
this.log[lvl] = (...a) => {
this.log_context[lvl](...a);
}
};
});

this.only_one_preinit_fn = null;
this.only_one_init_fn = null;

this.registry = {
register: this.register.bind(this),
of: (typeKey) => {
Expand All @@ -90,83 +90,77 @@ class Extension extends AdvancedBase {
...Object.values(this.registry_[typeKey].named),
...this.registry_[typeKey].anonymous,
],
}
}
};
},
};
}

example () {
example() {
console.log('Example method called by an extension.');
}

// === [START] RuntimeModule aliases ===
set exports (value) {
set exports(value) {
this.runtime.exports = value;
}
get exports () {
get exports() {
return this.runtime.exports;
}
import (name) {
import(name) {
return this.runtime.import(name);
}
// === [END] RuntimeModule aliases ===

/**
* This will get a database instance from the default service.
*/
get db () {
get db() {
const db = this.service.values.get('db');
if ( ! db ) {
throw new Error(
'extension tried to access database before it was ' +
'initialized'
);
throw new Error('extension tried to access database before it was ' +
'initialized');
}
return db;
}

get services () {
get services() {
const services = this.service.values.get('services');
if ( ! services ) {
throw new Error(
'extension tried to access "services" before it was ' +
'initialized'
);
throw new Error('extension tried to access "services" before it was ' +
'initialized');
}
return services;
}

get log_context () {
get log_context() {
const log_context = this.service.values.get('log_context');
if ( ! log_context ) {
throw new Error(
'extension tried to access "log_context" before it was ' +
'initialized'
);
throw new Error('extension tried to access "log_context" before it was ' +
'initialized');
}
return log_context;
}

/**
* Register anonymous or named data to a particular type/category.
* @param {string} typeKey Type of data being registered
* @param {string} [key] Key of data being registered
* @param {any} data The data to be registered
*/
register (typeKey, keyOrData, data) {
register(typeKey, keyOrData, data) {
if ( ! this.registry_[typeKey] ) {
this.registry_[typeKey] = {
named: {},
anonymous: [],
};
}

const typeRegistry = this.registry_[typeKey];

if ( arguments.length <= 1 ) {
throw new Error('you must specify what to register');
}

if ( arguments.length === 2 ) {
data = keyOrData;
if ( Array.isArray(data) ) {
Expand All @@ -178,28 +172,28 @@ class Extension extends AdvancedBase {
typeRegistry.anonymous.push(data);
return;
}

const key = keyOrData;
typeRegistry.named[key] = data;
}

/**
* Alias for .register()
* @param {string} typeKey Type of data being registered
* @param {string} [key] Key of data being registered
* @param {any} data The data to be registered
*/
reg (...a) {
reg(...a) {
this.register(...a);
}

/**
* This will create a GET endpoint on the default service.
* @param {*} path - route for the endpoint
* @param {*} handler - function to handle the endpoint
* @param {*} options - options like noauth (bool) and mw (array)
*/
get (path, handler, options) {
get(path, handler, options) {
// this extension will have a default service
this.ensure_service_();

Expand All @@ -221,7 +215,7 @@ class Extension extends AdvancedBase {
* @param {*} handler - function to handle the endpoint
* @param {*} options - options like noauth (bool) and mw (array)
*/
post (path, handler, options) {
post(path, handler, options) {
// this extension will have a default service
this.ensure_service_();

Expand All @@ -236,8 +230,52 @@ class Extension extends AdvancedBase {
methods: ['POST'],
});
}

use (...args) {

/**
* This will create a DELETE endpoint on the default service.
* @param {*} path - route for the endpoint
* @param {*} handler - function to handle the endpoint
* @param {*} options - options like noauth (bool) and mw (array)
*/
put(path, handler, options) {
// this extension will have a default service
this.ensure_service_();

// handler and options may be flipped
if ( typeof handler === 'object' ) {
[handler, options] = [options, handler];
}
if ( ! options ) options = {};

this.service.register_route_handler_(path, handler, {
...options,
methods: ['PUT'],
});
}
/**
* This will create a DELETE endpoint on the default service.
* @param {*} path - route for the endpoint
* @param {*} handler - function to handle the endpoint
* @param {*} options - options like noauth (bool) and mw (array)
*/

delete(path, handler, options) {
// this extension will have a default service
this.ensure_service_();

// handler and options may be flipped
if ( typeof handler === 'object' ) {
[handler, options] = [options, handler];
}
if ( ! options ) options = {};

this.service.register_route_handler_(path, handler, {
...options,
methods: ['DELETE'],
});
}

use(...args) {
this.ensure_service_();
this.service.expressThings_.push({
type: 'router',
Expand All @@ -246,7 +284,7 @@ class Extension extends AdvancedBase {
}

get preinit() {
return (function (callback) {
return (function(callback) {
this.on('preinit', callback);
}).bind(this);
}
Expand All @@ -257,7 +295,8 @@ class Extension extends AdvancedBase {
});
}
if ( callback === null ) {
this.only_one_preinit_fn = () => {};
this.only_one_preinit_fn = () => {
};
}
this.only_one_preinit_fn = callback;
}
Expand All @@ -267,19 +306,20 @@ class Extension extends AdvancedBase {
this.on('init', callback);
}).bind(this);
}
set init (callback) {
set init(callback) {
if ( this.only_one_init_fn === null ) {
this.on('init', (...a) => {
this.only_one_init_fn(...a);
});
}
if ( callback === null ) {
this.only_one_init_fn = () => {};
this.only_one_init_fn = () => {
};
}
this.only_one_init_fn = callback;
}

get console () {
get console() {
const extensionConsole = Object.create(console);
const logfn = level => (...a) => {
let svc_log;
Expand Down Expand Up @@ -317,10 +357,10 @@ class Extension extends AdvancedBase {
* This method will create the "default service" for an extension.
* This is specifically for Puter extensions that do not define their
* own service classes.
*
*
* @returns {void}
*/
ensure_service_ () {
ensure_service_() {
if ( this.service ) {
return;
}
Expand All @@ -333,4 +373,4 @@ class Extension extends AdvancedBase {

module.exports = {
Extension,
}
};
2 changes: 1 addition & 1 deletion src/backend/src/modules/web/WebServerService.js
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ class WebServerService extends BaseService {

const allowed_headers = [
"Origin", "X-Requested-With", "Content-Type", "Accept", "Authorization", "sentry-trace", "baggage",
"Depth", "Destination", "Overwrite", "If", "Lock-Token", "DAV"
"Depth", "Destination", "Overwrite", "If", "Lock-Token", "DAV", "stripe-signature",
];

// Request headers to allow
Expand Down
Loading
Loading