- risolto problema della non attesa della PWA durante la chiamata a Node.js.

- risolto problema dell'ambiente in Locale HTTPS certificato installato aggiornato.
This commit is contained in:
Surya Paolo
2025-03-13 12:05:16 +01:00
parent 65b29a6eee
commit 0017f04e45
12 changed files with 370 additions and 223 deletions

View File

@@ -21,8 +21,8 @@ DELAY_SENDEMAIL=2000
VAPI_KEY_SUBJECT=mailto:paolo@freeplanet.app
PUBLIC_VAPI_KEY=BDncvMiUZmjaCG2Kr1V9N0_33hOG-AuNSbHSvL24y2dzBiUjAxKm02emx5SeJvz2IGmtRf6YqCgopeQwCwUmZw8
PRIVATE_VAPI_KEY=uB2-jQkrbysyDtqN3ziMBDsVn0wdEaDsksX81zoOGQo
PATH_CERT_KEY=localhost.key
PATH_SERVER_CRT=localhost.crt
PATH_CERT_KEY=localhost-key.pem
PATH_SERVER_CRT=localhost.pem
PATH_SSL_ROOT_PEM=root.pem
PATH_SSL_CHAIN_PEM=chain.pem
GCM_API_KEY=""

View File

@@ -21,8 +21,8 @@ DELAY_SENDEMAIL=2000
VAPI_KEY_SUBJECT=mailto:paolo@freeplanet.app
PUBLIC_VAPI_KEY=BDncvMiUZmjaCG2Kr1V9N0_33hOG-AuNSbHSvL24y2dzBiUjAxKm02emx5SeJvz2IGmtRf6YqCgopeQwCwUmZw8
PRIVATE_VAPI_KEY=uB2-jQkrbysyDtqN3ziMBDsVn0wdEaDsksX81zoOGQo
PATH_CERT_KEY=localhost.key
PATH_SERVER_CRT=localhost.crt
PATH_CERT_KEY=localhost-key.pem
PATH_SERVER_CRT=localhost.pem
PATH_SSL_ROOT_PEM=root.pem
PATH_SSL_CHAIN_PEM=chain.pem
GCM_API_KEY=""

1
localhost-key.pem Symbolic link
View File

@@ -0,0 +1 @@
/Users/suryapaolo/certs/localhost-key.pem

1
localhost.pem Symbolic link
View File

@@ -0,0 +1 @@
/Users/suryapaolo/certs/localhost.pem

View File

@@ -30,10 +30,12 @@ const authenticate = async (req, res, next) => {
const access = 'auth';
const idapp = getIdApp(req);
//const idapp = getIdApp(req);
try {
const ris = await User.findByToken(token, access, true, idapp);
console.log(' ### Authenticate: token', !!token);
const ris = await User.findByToken(token, access, true, false);
if (ris && ris.user && !!ris.user.deleted) {
if (ris.user.deleted)
ris.user = null;
@@ -48,6 +50,7 @@ const authenticate = async (req, res, next) => {
}
if (!!ris.user) {
console.log(' AUTH 2) ');
// crea una funzione per aggiornare il lasttimeonline e useragent
// Save last time online
const myuser = await User.updateLastTimeAndUserAgent(ris.user._id, req.get('User-Agent'));
@@ -55,6 +58,68 @@ const authenticate = async (req, res, next) => {
req.token = token;
// req.refreshToken = refreshToken;
req.access = access;
console.log(' AUTH 3) NEXT... ');
next(); // Esegui il codice successivo
}
} catch (e) {
tools.mylog("ERR authenticate invalid Token =", e);
if (e === server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED) {
return res.status(server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED).send();
}
res.status(server_constants.RIS_CODE_HTTP_INVALID_TOKEN).send();
}
};
const authenticate_withUser = async (req, res, next) => {
const token = req.header('x-auth');
//const refreshToken = req.header('x-refrtok');
// console.log('authenticate... ');
let noaut = false;
if (req.body.hasOwnProperty('noaut')) {
noaut = req.body.noaut;
}
if (noaut) {
next();
return;
}
const access = 'auth';
//const idapp = getIdApp(req);
try {
console.log(' ### authenticate_withUser: token', !!token);
const ris = await User.findByToken(token, access, true, true);
if (ris && ris.user && !!ris.user.deleted) {
if (ris.user.deleted)
ris.user = null;
}
if (ris.code === server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED) {
return res.status(server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED).send();
}
if (!ris.user) {
return res.status(server_constants.RIS_CODE_HTTP_INVALID_TOKEN).send();
}
if (!!ris.user) {
console.log(' AUTH 2) ');
// crea una funzione per aggiornare il lasttimeonline e useragent
// Save last time online
await User.updateLastTimeAndUserAgent(ris.user._id, req.get('User-Agent'));
req.user = ris.user;
req.token = token;
// req.refreshToken = refreshToken;
req.access = access;
console.log(' AUTH_WITHUSER 3) NEXT... ');
next(); // Esegui il codice successivo
}
} catch (e) {
@@ -91,7 +156,48 @@ const authenticate_noerror = async (req, res, next) => {
return next();
}
const ris = await User.findByToken(token, 'auth', false, getIdApp(req));
const ris = await User.findByToken(token, 'auth', false, false);
if (ris.code !== server_constants.RIS_CODE_OK) {
req.user = null;
req.token = null;
req.code = ris.code;
} else {
req.user = ris.user;
req.token = token;
req.refreshToken = refreshToken;
req.code = ris.code;
}
if (ris.code === server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED) {
return res.status(server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED).send();
}
console.log(' ## NEXT ! AVANTI...');
next();
} catch (e) {
console.error('Errore nel middleware di autenticazione:', e);
req.user = null;
req.token = null;
req.code = server_constants.RIS_CODE_HTTP_INVALID_TOKEN;
next();
}
};
const authenticate_noerror_WithUser = async (req, res, next) => {
try {
const token = req.header('x-auth');
const refreshToken = req.header('x-refrtok');
console.log(' ### Authenticate_noerror: token', !!token);
if (!token) {
req.user = null;
req.token = null;
req.code = server_constants.RIS_CODE_HTTP_INVALID_TOKEN;
console.log(' ## WITHUSER_TOKEN INVALIDO ❌ ...');
return next();
}
const ris = await User.findByToken(token, 'auth', false, true);
if (ris.code !== server_constants.RIS_CODE_OK) {
req.user = null;
@@ -119,4 +225,46 @@ const authenticate_noerror = async (req, res, next) => {
}
};
module.exports = { authenticate, authenticate_noerror, auth_default };
const authenticate_noerror_WithUserLean = async (req, res, next) => {
try {
const token = req.header('x-auth');
const refreshToken = req.header('x-refrtok');
console.log(' ### Authenticate_noerror: token', !!token);
if (!token) {
req.user = null;
req.token = null;
req.code = server_constants.RIS_CODE_HTTP_INVALID_TOKEN;
console.log(' ## WITHUSER_TOKEN INVALIDO ❌ ...');
return next();
}
const ris = await User.findByToken(token, 'auth', false, true, true);
if (ris.code !== server_constants.RIS_CODE_OK) {
req.user = null;
req.token = null;
req.code = ris.code;
} else {
req.user = ris.user;
req.token = token;
req.refreshToken = refreshToken;
req.code = ris.code;
}
if (ris.code === server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED) {
return res.status(server_constants.RIS_CODE_HTTP_FORBIDDEN_TOKEN_EXPIRED).send();
}
console.log(' ## NEXT ! AVANTI...');
next();
} catch (e) {
console.error('Errore nel middleware di autenticazione:', e);
req.user = null;
req.token = null;
req.code = server_constants.RIS_CODE_HTTP_INVALID_TOKEN;
next();
}
};
module.exports = { authenticate, authenticate_noerror, auth_default, authenticate_withUser, authenticate_noerror_WithUser, authenticate_noerror_WithUserLean };

View File

@@ -441,7 +441,7 @@ MyGroupSchema.statics.getGroupsByUsername = async function (idapp, username, req
listgroups,
listSentRequestGroups,
listRefusedGroups,
mygroups: req.user.profile.mygroups,
mygroups: await User.getMyGroupsById(req.user._id),
};
} catch (e) {

View File

@@ -731,7 +731,23 @@ UserSchema.statics.isFacilitatore = function (perm) {
}
};
UserSchema.statics.findByToken = async function (token, typeaccess, con_auth, idapp) {
/**
* Finds a user by their authentication token.
*
* @param {string} token - The authentication token.
* @param {string} typeaccess - The type of access associated with the token.
* @param {boolean} con_auth - Whether to continue authentication if the token is expired.
* @param {string} idapp - The application ID.
* @returns {Promise<Object>} An object containing the user and a status code, indicating
* whether the token is valid, expired, or invalid.
* The user object is null if no user is found or the token is invalid.
*
* This function verifies the provided token and retrieves the corresponding user if the token is valid.
* If the token is expired and `con_auth` is false, or if the token is invalid, it returns null for the user.
* The status code reflects the validity of the token: valid, expired, or invalid.
*/
UserSchema.statics.findByToken = async function (token, typeaccess, con_auth, withuser, withlean = false) {
const User = this;
let code = server_constants.RIS_CODE_HTTP_INVALID_TOKEN;
let user = null;
@@ -752,6 +768,10 @@ UserSchema.statics.findByToken = async function (token, typeaccess, con_auth, id
return { user: null, code };
}
let project = undefined;
if (withuser) {
if (withlean) {
user = await User.findOne({
_id: decoded.smart,
tokens: {
@@ -760,7 +780,32 @@ UserSchema.statics.findByToken = async function (token, typeaccess, con_auth, id
access: typeaccess,
},
},
}).lean();
}, project).lean();
} else {
user = await User.findOne({
_id: decoded.smart,
tokens: {
$elemMatch: {
token,
access: typeaccess,
},
},
}, project);
}
} else {
project = { perm: 1, _id: 1, idapp: 1, username: 1, deleted: 1, aportador_solidario: 1, aportador_solidario_nome_completo: 1, 'profile.socioresidente': 1 };
user = await User.findOne({
_id: decoded.smart,
tokens: {
$elemMatch: {
token,
access: typeaccess,
},
},
}, project).lean();
}
if (user) {
const checkExpiry = tools.getEnableTokenExpiredByIdApp(user.idapp);
@@ -791,13 +836,13 @@ UserSchema.statics.findByTokenAnyAccess = function (token) {
}).lean();
};
UserSchema.statics.findByCredentials = function (idapp, username, password, pwdcrypted) {
UserSchema.statics.findByCredentials = async function (idapp, username, password, pwdcrypted) {
const User = this;
let pwd = '';
let regexp = new RegExp(`^${username}$`, 'i');
return User.findOne({
let user = await User.findOne({
idapp,
username: { $regex: regexp },
$or: [
@@ -811,34 +856,32 @@ UserSchema.statics.findByCredentials = function (idapp, username, password, pwdc
},
],
}).then((user) => {
});
if (!user) {
// Check if with email:
return User.findOne({
user = await User.findOne({
idapp, email: username.toLowerCase(),
$or: [
{ deleted: { $exists: false } },
{ deleted: { $exists: true, $eq: false } }],
});
} else {
return !user.deleted || (user.deleted && user.subaccount) ? user : null;
}
}).then((user) => {
if (!user) {
// Check with username telegram
return User.findOne({
user = await User.findOne({
idapp,
'profile.username_telegram': username.toLowerCase(),
$or: [
{ deleted: { $exists: false } },
{ deleted: { $exists: true, $eq: false } }],
});
} else {
return !user.deleted || (user.deleted && user.subaccount) ? user : null;
}
}).then(user => {
if (!user)
if (!user) {
return null;
}
pwd = user.password;
@@ -850,19 +893,9 @@ UserSchema.statics.findByCredentials = function (idapp, username, password, pwdc
}
}
return new Promise((resolve, reject) => {
// Use bcrypt.compare to compare password and user.password
// console.log("pwd1 " + password);
// console.log("pwd2 " + pwd);
bcrypt.compare(password, pwd, (err, res) => {
if (res) {
resolve(user);
} else {
return resolve(null);
}
});
});
});
const res = await bcrypt.compare(password, pwd) ? user : null;
return res;
};
UserSchema.statics.findByUsername = async function (idapp, username, alsoemail, onlyifVerifiedByAportador) {
@@ -1885,6 +1918,7 @@ UserSchema.statics.getUserProfileByUsername = async function (
if (perm === tools.Perm.PERM_NONE) {
whatToShow = {
idapp: 1,
lang: 1,
index: 1,
username: 1,
@@ -1935,6 +1969,7 @@ UserSchema.statics.getUserProfileByUsername = async function (
} else if (perm === tools.Perm.PERM_FRIEND) {
whatToShow = {
idapp: 1,
lang: 1,
index: 1,
username: 1,
@@ -1984,6 +2019,7 @@ UserSchema.statics.getUserProfileByUsername = async function (
} else if (perm === tools.Perm.PERM_ALL) {
whatToShow = {
idapp: 1,
lang: 1,
index: 1,
username: 1,
@@ -3333,7 +3369,7 @@ UserSchema.statics.setCircuitCmd = async function (idapp, usernameOrig, circuitn
await telegrambot.sendMsgTelegram(idapp, username_dest, msgDest);
msgerr = i18n.__('EXCEED_QTAMAX_MITTENTE', username_dest);
await telegrambot.sendMsgTelegram(idapp, usernameOrig, msgDest);
await telegrambot.sendMsgTelegram(idapp, usernameOrig, msgerr);
}
console.warn('🔴 ATTENZIONE! ', outres.errormsg + '\n(Mittente: ' + usernameOrig + ')');
@@ -6295,6 +6331,14 @@ UserSchema.statics.updateLastTimeAndUserAgent = async function (id, useragent) {
return ris;
}
UserSchema.statics.getMyGroupsById = async function (id) {
const User = this;
// cerca lo user by id e ritorna "profile.mygroups"
const ris = await User.findOne({ _id: id }, { 'profile.mygroups': 1 }).lean();
return ris;
};
UserSchema.statics.createNewSubRecord = async function (idapp, req) {
const User = this;

View File

@@ -351,7 +351,6 @@ router.post('/:userId/ordercartstatus', authenticate, async function (req, res,
let idapp = req.body.idapp;
let userId = req.params.userId;
let order_id = req.body.order_id;
const user = req.user;
let status = req.body.status;
let options = req.body.options;
@@ -403,7 +402,7 @@ router.post('/:userId/ordercartstatus', authenticate, async function (req, res,
let orderscart = null;
if (User.isManager(user.perm)) {
if (User.isManager(req.user.perm)) {
// Prende Tutti gli Ordini !
orderscart = await OrdersCart.getOrdersCartByUserId('ALL', idapp, 0, false);
} else {
@@ -424,7 +423,6 @@ router.post('/:userId/ordercartstatus', authenticate, async function (req, res,
//POST cart
router.post('/:userId/gestord', authenticate, async function (req, res, next) {
let idapp = req.body.idapp;
const user = req.user;
let idGasordine = req.body.idGasordine;
const { User } = require('../models/user');

View File

@@ -9,7 +9,7 @@ const i18n = require('i18n');
const sharp = require('sharp');
const { authenticate, authenticate_noerror } = require(
const { authenticate, authenticate_noerror, authenticate_noerror_WithUser, authenticate_noerror_WithUserLean } = require(
'../middleware/authenticate');
const { ObjectId } = require('mongodb');
@@ -1793,19 +1793,34 @@ router.post('/duprec/:table/:id', authenticate, async (req, res) => {
});
router.get('/loadsite/:userId/:idapp', authenticate_noerror, (req, res) => {
router.get('/loadsite/:userId/:idapp', authenticate_noerror_WithUserLean, (req, res) => {
load(req, res, '0');
});
router.get('/loadsite/:userId/:idapp/:vers', authenticate_noerror,
async (req, res) => {
router.get('/testpao', async (req, res) => {
try {
// Simulazione di un'operazione asincrona (es. chiamata a DB o altro)
// await new Promise(resolve => setTimeout(resolve, 2000));
res.status(200).send('OK');
} catch (error) {
console.error('Errore durante il caricamento del sito:', error);
res.status(500).json({ error: 'Errore interno del server: TEST' });
}
});
router.get('/loadsite/:userId/:idapp/:vers', authenticate_noerror_WithUserLean, async (req, res) => {
try {
let versionstr = req.params.vers;
let version = tools.getVersionint(versionstr);
return await load(req, res, version);
});
} catch (error) {
console.error('Errore durante il caricamento del sito:', error);
res.status(500).json({ error: 'Errore interno del server' });
}
});
async function load(req, res, version = '0') {
try {

View File

@@ -31,7 +31,7 @@ const _ = require('lodash');
const reg = require('../reg/registration');
const { authenticate, authenticate_noerror } = require('../middleware/authenticate');
const { authenticate, authenticate_noerror, authenticate_withUser } = require('../middleware/authenticate');
const Cart = require('../models/cart');
@@ -55,13 +55,13 @@ const mongoose = require('mongoose').set('debug', false);
const Subscription = require('../models/subscribers');
function existSubScribe(userId, access, browser) {
return Subscription.findOne({ userId, access, browser }).then(itemsub => {
async function existSubScribe(userId, access, browser) {
try {
const itemsub = await Subscription.findOne({ userId, access, browser }).lean();
return itemsub;
}).catch(err => {
} catch (err) {
return null;
});
}
}
function getMobileComplete(user) {
@@ -445,7 +445,6 @@ router.patch('/:id', authenticate, (req, res) => {
});
router.post('/lastmovs', authenticate, async (req, res) => {
const username = req.user ? req.user.username : '';
const nummov = req.body.nummov;
const idapp = req.body.idapp;
@@ -502,7 +501,6 @@ router.post('/profile', authenticate, (req, res) => {
const perm = req.user ? req.user.perm : tools.Perm.PERM_NONE;
const username = req.body['username'];
const idapp = req.body.idapp;
const locale = req.body.locale;
//++Todo: controlla che tipo di dati ha il permesso di leggere
@@ -712,11 +710,10 @@ function checkBlocked(req, res, next) {
next();
}
router.post('/login', checkBlocked, (req, res) => {
var body = _.pick(req.body,
router.post('/login', checkBlocked, async (req, res) => {
const body = _.pick(req.body,
['username', 'password', 'idapp', 'keyappid', 'lang']);
var user = new User(body);
const userpass = new User(body);
// const subs = _.pick(req.body, ['subs']);
// tools.mylog("LOG: u: " + user.username + " p:" + user.password);
@@ -728,23 +725,21 @@ router.post('/login', checkBlocked, (req, res) => {
let resalreadysent = false;
const myuser = user;
try {
const user = await User.findByCredentials(userpass.idapp, userpass.username, userpass.password);
return User.findByCredentials(user.idapp, user.username, user.password).
then(async (user) => {
// tools.mylog("CREDENZIALI ! ");
if (!user) {
const rislogin = await User.tooManyLoginWrong(body.idapp, body.username, true);
if (rislogin.troppilogin) {
let text = 'Troppe richieste di Login ERRATE: ' + body.username + ' [IP: ' + tools.getiPAddressUser(req) + '] Tentativi: ' + rislogin.retry_pwd;
telegrambot.sendMsgTelegramToTheManagers(body.idapp, text);
console.log('/login', text);
res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: text });
return false;
return;
}
await tools.snooze(3000);
await tools.snooze(2000);
if (!failedLoginAttempts[body.username]) {
failedLoginAttempts[body.username] = 1;
@@ -760,7 +755,6 @@ router.post('/login', checkBlocked, (req, res) => {
await telegrambot.sendMsgTelegramToTheAdmin(myuser.idapp, msg, true);
tools.writeErrorLog(msg);
}
// telegrambot.sendMsgTelegramToTheManagers(body.idapp, msg);
if (failedLoginAttempts[body.username] >= MAX_FAILED_ATTEMPTS) {
blockUser(body.username);
@@ -773,90 +767,47 @@ router.post('/login', checkBlocked, (req, res) => {
res.status(401).send({ code: server_constants.RIS_CODE_LOGIN_ERR });
resalreadysent = true;
}
return user;
}).
then(user => {
// console.log('Lgn-Ok');
if (user) {
return user.generateAuthToken(req).then((ris) => {
var usertosend = new User();
const myris = await user.generateAuthToken(req);
const usertosend = new User();
shared_consts.fieldsUserToChange().forEach((field) => {
usertosend[field] = user[field];
});
// usertosend._id = user._id.toHexString();
// if (!User.isAdmin(req.user)) {
// usertosend.ipaddr = user.ipaddr;
// }
const subsExistonDb = await existSubScribe(usertosend._id, 'auth', req.get('User-Agent'));
// tools.mylog("user.verified_email:" + user.verified_email);
// tools.mylog("usertosend.userId", usertosend.userId);
return { usertosend, token: ris.token, refreshToken: ris.refreshToken };
}).then((myris) => {
const access = 'auth';
const browser = req.get('User-Agent');
// Check if already exist Subscribe
return existSubScribe(myris.usertosend._id, access, browser).
then(subscribe => {
return (subscribe !== null);
}).
then(subsExistonDb => {
// console.log('ESEGUITO OK')
return {
usertosend: myris.usertosend,
token: myris.token,
refreshToken: myris.refreshToken,
subsExistonDb,
};
}).
catch(err => {
return {
usertosend: myris.usertosend,
token: myris.token,
refreshToken: myris.refreshToken,
subsExistonDb: false,
};
});
}).then(myris => {
// console.log('res', myris.token, myris.usertosend);
// SEND TOKEN AND CODE RESULT
return res
res
.header('x-auth', myris.token)
.header('x-refrtok', myris.refreshToken)
.send({
usertosend: myris.usertosend,
usertosend,
code: server_constants.RIS_CODE_OK,
subsExistonDb: myris.subsExistonDb,
subsExistonDb,
});
// tools.mylog("TROVATOOO!");
// tools.mylog('FINE LOGIN')
});
}
}).
catch((e) => {
} catch (e) {
console.error('ERRORE IN LOGIN: ' + e.message);
if (!resalreadysent)
res.status(400).
send({ code: server_constants.RIS_CODE_LOGIN_ERR_GENERIC, msgerr: e.message });
});
}
});
router.delete('/me/token', authenticate, (req, res) => {
router.delete('/me/token', authenticate_withUser, (req, res) => {
// tools.mylog("TOKENREM = " + req.token);
try {
req.user.removeToken(req.token).then(() => {
res.status(200).send();
}, () => {
res.status(400).send();
});
} catch (e) {
console.log('delete(/me/token', e.message);
}
});
router.post('/setperm', authenticate, (req, res) => {

View File

@@ -754,19 +754,6 @@ connectToDatabase(connectionUrl, options)
return { key: privateKey, cert: certificate };
}
} else if (process.env.HTTPS_LOCALHOST === "true") {
try {
return {
key: fs.readFileSync(process.env.PATH_CERT_KEY, 'utf8'),
cert: fs.readFileSync(process.env.PATH_SERVER_CRT, 'utf8'),
ciphers: 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES256-SHA384',
honorCipherOrder: true,
secureProtocol: 'TLSv1_2_method'
};
} catch (error) {
console.error('Errore durante la lettura dei file di certificazione, error:', error.message);
throw error;
}
}
// Caso di default non specificato, potrebbe essere necessario aggiungere una gestione degli errori qui
}
@@ -810,7 +797,7 @@ connectToDatabase(connectionUrl, options)
let allowedOrigins = null;
if (!isProduction) {
allowedOrigins = 'http://localhost:3000';
allowedOrigins = 'https://localhost:3000';
} else {
allowedOrigins = domains.flatMap(domain => [
@@ -819,6 +806,7 @@ connectToDatabase(connectionUrl, options)
`https://test.${domain.hostname}`,
`https://testapi.${domain.hostname}`,
`https://freeplanet.app:3000`,
`https://freeplanet.app:3001`,
`http://${domain.hostname}`,
`http://api.${domain.hostname}`,
`http://test.${domain.hostname}`,
@@ -834,6 +822,7 @@ connectToDatabase(connectionUrl, options)
try {
// Validazione dell'input
if (!origin || typeof origin !== 'string' || !/^https?:\/\/[^\s/$.?#].[^\s]*$/.test(origin)) {
if (origin)
console.error('❌ Origine non valida', origin);
}

View File

@@ -1 +1 @@
1.2.13
1.2.14