BaseImplementation.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /*
  2. * Copyright (C) 2024 Puter Technologies Inc.
  3. *
  4. * This file is part of Puter.
  5. *
  6. * Puter is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. const { AdvancedBase } = require("@heyputer/puter-js-common");
  20. const { Context } = require("../../../util/context");
  21. const APIError = require("../../../api/APIError");
  22. const { AppUnderUserActorType, Actor, UserActorType } = require("../../auth/Actor");
  23. const { BaseOperation } = require("../../OperationTraceService");
  24. const { CodeUtil } = require("../../../codex/CodeUtil");
  25. /**
  26. * Base class for all driver implementations.
  27. */
  28. class BaseImplementation extends AdvancedBase {
  29. constructor (...a) {
  30. super(...a);
  31. const methods = this._get_merged_static_object('METHODS');
  32. // Turn each method into an operation
  33. for ( const k in methods ) {
  34. methods[k] = CodeUtil.mrwrap(methods[k], BaseOperation, {
  35. name: `${this.constructor.ID}:${k}`,
  36. });
  37. };
  38. this.methods = methods;
  39. this.sla = this._get_merged_static_object('SLA');
  40. }
  41. async call (method, args) {
  42. if ( ! this.methods[method] ) {
  43. throw new Error(`method not found: ${method}`);
  44. }
  45. const pseudo_this = Object.assign({}, this);
  46. const context = Context.get();
  47. pseudo_this.context = context;
  48. pseudo_this.services = context.get('services');
  49. const services = context.get('services');
  50. pseudo_this.log = services.get('log-service').create(this.constructor.name);
  51. await this._sla_enforcement(method);
  52. return await this.methods[method].call(pseudo_this, args);
  53. }
  54. async _sla_enforcement (method) {
  55. const context = Context.get();
  56. const services = context.get('services');
  57. const method_key = `${this.constructor.ID}:${method}`;
  58. const svc_sla = services.get('sla');
  59. // System SLA enforcement
  60. {
  61. const sla_key = `driver:impl:${method_key}`;
  62. const sla = await svc_sla.get('system', sla_key);
  63. const sys_method_key = `system:${method_key}`;
  64. // short-term rate limiting
  65. if ( sla?.rate_limit ) {
  66. const svc_rateLimit = services.get('rate-limit');
  67. let eventual_success = false;
  68. for ( let i = 0 ; i < 60 ; i++ ) {
  69. try {
  70. await svc_rateLimit.check_and_increment(sys_method_key, sla.rate_limit.max, sla.rate_limit.period);
  71. eventual_success = true;
  72. break;
  73. } catch ( e ) {
  74. if (
  75. ! ( e instanceof APIError ) ||
  76. e.fields.code !== 'rate_limit_exceeded'
  77. ) throw e;
  78. await new Promise((resolve) => setTimeout(resolve, 1000));
  79. }
  80. }
  81. if ( ! eventual_success ) {
  82. throw APIError.create('server_rate_exceeded');
  83. }
  84. }
  85. }
  86. // test_mode is checked to prevent rate limiting when it is enabled
  87. const test_mode = context.get('test_mode');
  88. // User SLA enforcement
  89. {
  90. const actor = context.get('actor').get_related_actor(UserActorType);
  91. const user_is_verified = !! actor.type.user.email_confirmed;
  92. const sla_key = `driver:impl:${method_key}`;
  93. const sla = await svc_sla.get(
  94. user_is_verified ? 'user_verified' : 'user_unverified',
  95. sla_key
  96. );
  97. console.log('SLA KEY', sla_key, 'USER KEY', user_is_verified ? 'user_verified' : 'user_unverified');
  98. const user_method_key = `actor:${actor.uid}:${method_key}`;
  99. // short-term rate limiting
  100. if ( sla?.rate_limit ) {
  101. const svc_rateLimit = services.get('rate-limit');
  102. await svc_rateLimit.check_and_increment(method_key, sla.rate_limit.max, sla.rate_limit.period);
  103. }
  104. // long-term rate limiting
  105. if ( sla?.monthly_limit && ! test_mode ) {
  106. const svc_monthlyUsage = services.get('monthly-usage');
  107. const count = await svc_monthlyUsage.check(
  108. actor, {
  109. 'driver.interface': this.constructor.INTERFACE,
  110. 'driver.implementation': this.constructor.ID,
  111. 'driver.method': method,
  112. });
  113. if ( count >= sla.monthly_limit ) {
  114. throw APIError.create('monthly_limit_exceeded', null, {
  115. method_key,
  116. limit: sla.monthly_limit,
  117. });
  118. }
  119. }
  120. }
  121. // App SLA enforcement
  122. await (async () => {
  123. const actor = context.get('actor');
  124. if ( ! ( actor.type instanceof AppUnderUserActorType ) ) return;
  125. const sla_key = `driver:impl:${method_key}`;
  126. const sla = await svc_sla.get('app_default', sla_key);
  127. // long-term rate limiting
  128. if ( sla?.monthly_limit && ! test_mode ) {
  129. const svc_monthlyUsage = services.get('monthly-usage');
  130. const count = await svc_monthlyUsage.check(
  131. actor, {
  132. 'driver.interface': this.constructor.INTERFACE,
  133. 'driver.implementation': this.constructor.ID,
  134. 'driver.method': method,
  135. });
  136. if ( count >= sla.monthly_limit ) {
  137. throw APIError.create('monthly_limit_exceeded', null, {
  138. method_key,
  139. limit: sla.monthly_limit,
  140. });
  141. }
  142. }
  143. })();
  144. // Record monthly usage
  145. if ( ! test_mode ) {
  146. const actor = context.get('actor');
  147. const svc_monthlyUsage = services.get('monthly-usage');
  148. const extra = {
  149. 'driver.interface': this.constructor.INTERFACE,
  150. 'driver.implementation': this.constructor.ID,
  151. 'driver.method': method,
  152. };
  153. await svc_monthlyUsage.increment(actor, method_key, extra);
  154. }
  155. }
  156. async get_response_meta () {
  157. return {
  158. driver: this.constructor.ID,
  159. driver_version: this.constructor.VERSION,
  160. driver_interface: this.constructor.INTERFACE,
  161. };
  162. }
  163. }
  164. module.exports = {
  165. BaseImplementation,
  166. };