@softvisio/core
Version:
Softisio core
382 lines (292 loc) • 10.4 kB
JavaScript
import Permissions from "#lib/app/user/permissions";
export default class Context {
#api;
#token;
#user;
#telegramBotUser;
#permissions;
#isDeleted;
#connection;
#abortSignal;
#hostname;
#userAgent;
#remoteAddress;
#method;
#isVoidCall;
#isPrivateCall;
constructor ( api, { token, user, telegramBotUser, permissions, isDeleted, connection, signal, hostname, userAgent, remoteAddress, method, isVoidCall, isPrivateCall } = {} ) {
this.#api = api;
this.#token = token;
this.#user = user;
this.#telegramBotUser = telegramBotUser;
this.#permissions = permissions;
this.#isDeleted = !!isDeleted;
this.#connection = connection;
this.#abortSignal = signal;
this.#hostname = hostname;
this.#userAgent = userAgent;
this.#remoteAddress = remoteAddress;
this.#method = method;
this.#isVoidCall = !!isVoidCall;
this.#isPrivateCall = !!isPrivateCall;
}
// properties
get app () {
return this.#api.app;
}
get api () {
return this.#api;
}
get token () {
return this.#token;
}
get user () {
return this.#user;
}
get telegramBot () {
return this.#telegramBotUser?.bot;
}
get telegramBotUser () {
return this.#telegramBotUser;
}
get permissions () {
return this.#permissions;
}
get isPrivateCall () {
return this.#isPrivateCall;
}
get isVoidCall () {
return this.#isVoidCall;
}
get connection () {
return this.#connection;
}
get abortSignal () {
return this.#abortSignal;
}
get hostname () {
return this.#hostname;
}
get userAgent () {
return this.#userAgent;
}
get remoteAddress () {
return this.#remoteAddress;
}
get method () {
return this.#method;
}
get isDeleted () {
return this.#isDeleted;
}
get isEnabled () {
if ( this.#token ) {
// token is disabled
if ( !this.#token.isEnabled ) return false;
}
else if ( this.#telegramBotUser ) {
// telegram bot user is disabled
if ( !this.#telegramBotUser.isEnabled ) return false;
}
if ( this.#user ) {
// user is disabled
if ( !this.#user.isEnabled ) return false;
}
return true;
}
// public
async call ( method, ...args ) {
return this.#call( method, args, false );
}
voidCall ( method, ...args ) {
this.#call( method, args, true );
}
updateLastActivity () {
if ( !this.#token ) return;
// context is deleted
if ( this.#isDeleted ) return;
this.#api.frontend.updateTokenLastActivity( this.#token );
}
async update () {
// context is deleted
if ( this.#isDeleted ) return true;
var userId;
// update token
if ( this.#token ) {
let token;
// refresh token
if ( this.#token.isApiToken ) {
token = this.#api.tokens.cache.getCachedTokenById( this.#token.id ) ?? ( await this.#api.tokens.cache.getTokenById( this.#token.id ) );
}
else if ( this.#token.isSessionToken ) {
token = this.#api.sessions.cache.getCachedSessionById( this.#token.id ) ?? ( await this.#api.sessions.cache.getSessionById( this.#token.id ) );
}
// backend error
if ( token === false ) {
return false;
}
// token was deleted
else if ( !token ) {
this.#isDeleted = true;
return true;
}
// store token
else {
this.#token = token;
}
userId = this.#token.userId;
}
// update telegram bot user
else if ( this.#telegramBotUser ) {
const telegramBot = this.app.telegram.bots.getBotById( this.#telegramBotUser.bot.id );
// telegram bot stopped / deleted
if ( !telegramBot?.isStarted ) {
this.#isDeleted = true;
return true;
}
const telegramBotUser = await telegramBot.users.getTelegramUserById( this.#telegramBotUser.id );
// backend error
if ( telegramBotUser === false ) {
return false;
}
// telegram bot user was unsubscribed
else if ( !telegramBotUser?.isSubscribed ) {
this.#isDeleted = true;
return true;
}
// store telegram bot user
else {
this.#telegramBotUser = telegramBotUser;
}
userId = this.#telegramBotUser.apiUserId;
}
// update user
if ( userId ) {
const user = this.app.users.getCachedUserById( userId ) ?? ( await this.app.users.getUserById( userId ) );
// backend error
if ( user === false ) {
return false;
}
// user was deleted
else if ( !user ) {
this.#isDeleted = true;
}
// store user
else {
this.#user = user;
}
}
return true;
}
// private
async #call ( methodId, args, isVoidCall ) {
var signal;
if ( typeof methodId === "object" ) {
( { "method": methodId, args, signal } = methodId );
}
signal ??= this.#abortSignal;
// aborted
if ( signal?.aborted ) return result( -32_817 );
const api = this.#api,
frontend = api.frontend,
isPrivateCall = this.#method
? true
: this.#isPrivateCall;
// reject public api calls if frontend is destroying
if ( frontend.isDestroying && !isPrivateCall ) {
return result( -32_816 );
}
// context is deleted
if ( this.#isDeleted ) return result( -32_815 );
// check method version, inherit version from the parent method
if ( !methodId.startsWith( "/" ) && this.#method ) methodId = "/v" + this.#method.version + "/" + methodId;
const method = api.schema.methods[ methodId ];
// method not found
if ( !method ) return result( -32_809 );
// method not implemented
if ( typeof method.object[ method.apiName ] !== "function" ) return result( -32_809 );
// check persistent connection
if ( method.requirePersistentConnection && !this.#connection ) return result( -32_810 );
// start call
const callDescriptor = frontend.startApiCall( {
"activityCounter": !method.requirePersistentConnection,
} );
var res;
try {
// update context
if ( this.#token || this.#telegramBotUser ) {
// api backend is down
if ( !api.isConnected ) throw result( -32_814 );
// unable to update context, api backend is down
if ( !( await this.update() ) ) throw result( -32_814 );
// aborted
if ( signal?.aborted ) throw result( -32_817 );
// context is deleted
if ( this.#isDeleted ) throw result( -32_815 );
// context is disabled
if ( !this.isEnabled ) throw result( -32_813 );
}
// check call limits
if ( api.isApi ) {
const allowCall = await frontend.checkApiCallLimits( callDescriptor, this, method, isPrivateCall );
// aborted
if ( signal?.aborted ) throw result( -32_817 );
// too many requests
if ( !allowCall ) throw result( -32_802 );
}
// validate method params
const validateParams = method.validateParams( args );
// params not valid
if ( !validateParams.ok ) throw validateParams;
let permissions;
if ( api.isApi ) {
permissions = await this.app.acl.resolveAclPermissions( this.#user?.id, validateParams.data.aclResolvers );
// error get permissions
if ( !permissions ) throw result( [ 500, "{ermissions check error" ] );
// check permissions
if ( method.permission ) {
if ( method.permission === "telegram-bot-users" ) {
if ( !this.#telegramBotUser ) throw result( -32_811 );
}
else if ( !permissions.has( method.permission ) ) {
throw result( -32_811 );
}
}
// check session authorization
if ( method.requireAuthorization && this.#token.isSessionToken && !this.#token.checkAuthorization( this.#remoteAddress, this.#userAgent ) ) {
throw result( -32_812 );
}
}
else {
permissions = Permissions.guestsPermissions;
}
// aborted
if ( signal?.aborted ) throw result( -32_817 );
// clone context
const ctx = new Context( api, {
"token": this.#token,
"user": this.#user,
"telegramBotUser": this.#telegramBotUser,
permissions,
"connection": this.#connection,
signal,
"hostname": this.#hostname,
"userAgent": this.#userAgent,
"remoteAddress": this.#remoteAddress,
isVoidCall,
isPrivateCall,
method,
} );
// call method
res = await api.app.monitoring.monitorMethodCall( api.isApi
? "api"
: "rpc", method.id, method.object[ method.apiName ].bind( method.object, ctx, ...args ) );
}
catch ( e ) {
res = e;
}
// finish call
frontend.endApiCall( callDescriptor );
return res;
}
}