mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-04 00:20:45 +00:00
metering: new usage endpoint + puter-js changes for it (#1738)
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test (20.x) (push) Has been cancelled
test / test (22.x) (push) Has been cancelled
test / api-test (22.x) (push) Has been cancelled
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test (20.x) (push) Has been cancelled
test / test (22.x) (push) Has been cancelled
test / api-test (22.x) (push) Has been cancelled
* metering: new usage endpoint * metering: new usage endpoint + puter-js changes for it
This commit is contained in:
@@ -0,0 +1 @@
|
||||
import './routes/usage.js';
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@heyputer/extension-metering-service",
|
||||
"main": "main.js",
|
||||
"type": "module"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/** @type {import('@heyputer/backend/src/services/MeteringService/MeteringServiceWrapper.mjs').MeteringAndBillingServiceWrapper} */
|
||||
const meteringAndBillingServiceWrapper = extension.import('service:meteringService');
|
||||
|
||||
// TODO DS: move this to its own router and just use under this path
|
||||
extension.get('/v2/usage', { subdomain: 'api' }, async (req, res) => {
|
||||
const meteringAndBillingService = meteringAndBillingServiceWrapper.meteringAndBillingService;
|
||||
|
||||
const actor = req.actor;
|
||||
if ( !actor ) {
|
||||
throw Error('actor not found in context');
|
||||
}
|
||||
const actorUsage = await meteringAndBillingService.getActorCurrentMonthUsageDetails(actor);
|
||||
res.status(200).json(actorUsage);
|
||||
return;
|
||||
});
|
||||
|
||||
extension.get('/v2/usage/:appId', { subdomain: 'api' }, async (req, res) => {
|
||||
const meteringAndBillingService = meteringAndBillingServiceWrapper.meteringAndBillingService;
|
||||
|
||||
const actor = req.actor;
|
||||
if ( !actor ) {
|
||||
throw Error('actor not found in context');
|
||||
}
|
||||
const appId = req.params.appId;
|
||||
if ( !appId ) {
|
||||
res.status(400).json({ error: 'appId parameter is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const appUsage = await meteringAndBillingService.getActorCurrentMonthAppUsageDetails(actor, appId);
|
||||
res.status(200).json(appUsage);
|
||||
return;
|
||||
});
|
||||
|
||||
console.debug('Loaded /v2/usage route');
|
||||
@@ -412,7 +412,7 @@ const install = async ({ context, services, app, useapi, modapi }) => {
|
||||
const { WorkerService } = require('./services/worker/WorkerService');
|
||||
services.registerService("worker-service", WorkerService);
|
||||
|
||||
const { MeteringAndBillingServiceWrapper } = require("./services/abuse-prevention/MeteringService/index.mjs");
|
||||
const { MeteringAndBillingServiceWrapper } = require("./services/MeteringService/MeteringServiceWrapper.mjs");
|
||||
services.registerService('meteringService', MeteringAndBillingServiceWrapper);
|
||||
|
||||
const { PermissionShortcutService } = require('./services/auth/PermissionShortcutService');
|
||||
|
||||
@@ -44,7 +44,7 @@ const VALID_ENGINES = ['standard', 'neural', 'long-form', 'generative'];
|
||||
* @extends BaseService
|
||||
*/
|
||||
class AWSPollyService extends BaseService {
|
||||
/** @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
meteringAndBillingService;
|
||||
|
||||
static MODULES = {
|
||||
|
||||
@@ -31,7 +31,7 @@ const { Context } = require("../../util/context");
|
||||
* Handles both S3-stored and buffer-based document processing with automatic region management.
|
||||
*/
|
||||
class AWSTextractService extends BaseService {
|
||||
/** @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
meteringAndBillingService;
|
||||
/**
|
||||
* AWS Textract service for OCR functionality
|
||||
|
||||
@@ -51,7 +51,7 @@ class ClaudeService extends BaseService {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
|
||||
/** @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
#meteringAndBillingService;
|
||||
|
||||
async _init() {
|
||||
|
||||
@@ -36,7 +36,7 @@ class DeepSeekService extends BaseService {
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService}
|
||||
* @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService}
|
||||
*/
|
||||
meteringAndBillingService;
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,7 @@ const { GoogleGenAI } = require('@google/genai');
|
||||
* the puter-image-generation interface.
|
||||
*/
|
||||
class GeminiImageGenerationService extends BaseService {
|
||||
/** @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
meteringAndBillingService;
|
||||
static MODULES = {
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ const { Context } = require("../../util/context");
|
||||
|
||||
class GeminiService extends BaseService {
|
||||
/**
|
||||
* @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService}
|
||||
* @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService}
|
||||
*/
|
||||
meteringAndBillingService = undefined;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ const BaseService = require("../../services/BaseService");
|
||||
const { Context } = require("../../util/context");
|
||||
const OpenAIUtil = require("./lib/OpenAIUtil");
|
||||
|
||||
/** @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
|
||||
/**
|
||||
* Service class for integrating with Groq AI's language models.
|
||||
@@ -34,7 +34,7 @@ const OpenAIUtil = require("./lib/OpenAIUtil");
|
||||
* @extends BaseService
|
||||
*/
|
||||
class GroqAIService extends BaseService {
|
||||
/** @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
meteringAndBillingService;
|
||||
static MODULES = {
|
||||
Groq: require('groq-sdk'),
|
||||
|
||||
@@ -31,7 +31,7 @@ const { Context } = require("../../util/context");
|
||||
* for different models and implements the puter-chat-completion interface.
|
||||
*/
|
||||
class MistralAIService extends BaseService {
|
||||
/** @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
meteringAndBillingService;
|
||||
static MODULES = {
|
||||
'@mistralai/mistralai': require('@mistralai/mistralai'),
|
||||
|
||||
@@ -31,7 +31,7 @@ const { Context } = require("../../util/context");
|
||||
* validation, and spending tracking.
|
||||
*/
|
||||
class OpenAIImageGenerationService extends BaseService {
|
||||
/** @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
meteringAndBillingService;
|
||||
|
||||
static MODULES = {
|
||||
|
||||
@@ -48,7 +48,7 @@ export class OpenAICompletionService {
|
||||
|
||||
#models;
|
||||
|
||||
/** @type {import('../../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('../../../services/MeteringService/MeteringService.js').MeteringAndBillingService} */
|
||||
#meteringAndBillingService;
|
||||
|
||||
constructor({ serviceName, config, globalConfig, aiChatService, meteringAndBillingService, models = OPEN_AI_MODELS, defaultModel = 'gpt-4.1-nano' }) {
|
||||
|
||||
@@ -46,7 +46,7 @@ class OpenRouterService extends BaseService {
|
||||
return model;
|
||||
}
|
||||
|
||||
/** @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
meteringAndBillingService;
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,7 @@ const { Context } = require("../../util/context");
|
||||
*/
|
||||
class TogetherAIService extends BaseService {
|
||||
/**
|
||||
* @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService}
|
||||
* @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService}
|
||||
*/
|
||||
meteringAndBillingService;
|
||||
static MODULES = {
|
||||
|
||||
@@ -33,7 +33,7 @@ class XAIService extends BaseService {
|
||||
static MODULES = {
|
||||
openai: require('openai'),
|
||||
};
|
||||
/** @type {import('../../services/abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('../../services/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
meteringAndBillingService;
|
||||
|
||||
adapt_model(model) {
|
||||
|
||||
+51
-42
@@ -1,12 +1,13 @@
|
||||
// @ts-ignore
|
||||
import type KVStoreInterface from "../../../modules/kvstore/KVStoreInterfaceService.js";
|
||||
import { SystemActorType, type Actor } from "../auth/Actor.js";
|
||||
// @ts-ignore
|
||||
import { SystemActorType, type Actor } from "../../auth/Actor.js";
|
||||
import type { AlarmService } from "../../modules/core/AlarmService.js";
|
||||
// @ts-ignore
|
||||
import type { AlarmService } from "../../../modules/core/AlarmService.js";
|
||||
import type { DBKVStore } from '../repositories/DBKVStore/DBKVStore.mjs';
|
||||
// @ts-ignore
|
||||
import type { SUService } from "../../SUService.js";
|
||||
import type { SUService } from "../SUService.js";
|
||||
import { COST_MAPS } from "./costMaps/index.js";
|
||||
import { SUB_POLICIES } from "./subPolicies/index.js";
|
||||
interface ActorWithType extends Actor {
|
||||
type: {
|
||||
app: { uid: string }
|
||||
@@ -26,14 +27,6 @@ interface UsageByType {
|
||||
[serviceName: string]: number
|
||||
}
|
||||
|
||||
|
||||
// NOTE: create daily and hourly entry buckets that expire at given ranges 2 days for hours, 6 months for daily
|
||||
// Store consumed microcents whenever a consumption event goes through
|
||||
// keep timestamp of consumption last updated to limit burst usage
|
||||
|
||||
const POLICY_TYPES = {
|
||||
'free': {} // TODO DS: define what needs to go here
|
||||
}
|
||||
const GLOBAL_APP_KEY = 'os-global'; // TODO DS: this should be loaded from config or db eventually
|
||||
const METRICS_PREFIX = 'metering';
|
||||
const POLICY_PREFIX = 'policy';
|
||||
@@ -44,16 +37,16 @@ const PERIOD_ESCAPE = '_dot_'; // to replace dots in usage types for kvstore pat
|
||||
*/
|
||||
export class MeteringAndBillingService {
|
||||
|
||||
#kvClientWrapper: KVStoreInterface
|
||||
#kvClientWrapper: DBKVStore
|
||||
#superUserService: SUService
|
||||
#alarmService: AlarmService
|
||||
constructor({ kvClientWrapper, superUserService, alarmService }: { kvClientWrapper: KVStoreInterface, superUserService: SUService, alarmService: AlarmService }) {
|
||||
constructor({ kvClientWrapper, superUserService, alarmService }: { kvClientWrapper: DBKVStore, superUserService: SUService, alarmService: AlarmService }) {
|
||||
this.#superUserService = superUserService;
|
||||
this.#kvClientWrapper = kvClientWrapper;
|
||||
this.#alarmService = alarmService;
|
||||
}
|
||||
|
||||
utilRecordUsageObject(trackedUsageObject: Record<string, number>, actor: Actor, modelPrefix: string) {
|
||||
utilRecordUsageObject(trackedUsageObject: Record<string, number>, actor: ActorWithType, modelPrefix: string) {
|
||||
Object.entries(trackedUsageObject).forEach(([usageKind, amount]) => {
|
||||
this.incrementUsage(actor, `${modelPrefix}:${usageKind}`, amount);
|
||||
});
|
||||
@@ -146,7 +139,6 @@ export class MeteringAndBillingService {
|
||||
});
|
||||
return { total: 0 } as UsageByType;
|
||||
}
|
||||
// TODO DS: this should increment the cost for the given type of operation, and the total cost for daily, weekly and monthly usage
|
||||
}
|
||||
|
||||
async getActorCurrentMonthUsageDetails(actor: ActorWithType) {
|
||||
@@ -159,54 +151,71 @@ export class MeteringAndBillingService {
|
||||
`${METRICS_PREFIX}:actor:${actor.type.user.uuid}:${currentMonth}`,
|
||||
`${METRICS_PREFIX}:actor:${actor.type.user.uuid}:apps:${currentMonth}`
|
||||
]
|
||||
return this.#superUserService.sudo(async () => {
|
||||
|
||||
return await this.#superUserService.sudo(async () => {
|
||||
const [usage, appTotals] = await this.#kvClientWrapper.get({ key: keys }) as [UsageByType | null, Record<string, UsageByType> | null];
|
||||
return {
|
||||
usage: usage || { total: 0 },
|
||||
appTotals: appTotals || {},
|
||||
// only show details of app based on actor, aggregate all as others, except if app is global one or null, then show all
|
||||
const appId = actor.type?.app?.uid
|
||||
if (appTotals && appId) {
|
||||
const filteredAppTotals: Record<string, UsageByType> = {};
|
||||
let othersTotal: UsageByType | null = null;
|
||||
Object.entries(appTotals).forEach(([appKey, appUsage]) => {
|
||||
if (appKey === appId) {
|
||||
filteredAppTotals[appKey] = appUsage;
|
||||
} else {
|
||||
Object.entries(appUsage).forEach(([usageKind, amount]) => {
|
||||
if (!othersTotal![usageKind]) {
|
||||
othersTotal![usageKind] = 0;
|
||||
}
|
||||
othersTotal![usageKind] += amount;
|
||||
})
|
||||
}
|
||||
});
|
||||
if (othersTotal) {
|
||||
filteredAppTotals['others'] = othersTotal;
|
||||
}
|
||||
return {
|
||||
usage: usage || { total: 0 },
|
||||
appTotals: filteredAppTotals,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
usage: usage || { total: 0 },
|
||||
appTotals: appTotals || {},
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async getActorCurrentMonthAppUsageDetails(actor: ActorWithType, appId: string) {
|
||||
async getActorCurrentMonthAppUsageDetails(actor: ActorWithType, appId?: string) {
|
||||
if (!actor.type?.user?.uuid) {
|
||||
throw new Error('Actor must be a user to get usage details');
|
||||
}
|
||||
|
||||
appId = appId || actor.type?.app?.uid || GLOBAL_APP_KEY;
|
||||
// batch get actor usage, per app usage, and actor app totals for the month
|
||||
const currentMonth = this.#getMonthYearString();
|
||||
const key = `${METRICS_PREFIX}:actor:${actor.type.user.uuid}:app:${appId}:${currentMonth}`
|
||||
|
||||
return this.#superUserService.sudo(async () => {
|
||||
return await this.#superUserService.sudo(async () => {
|
||||
const usage = await this.#kvClientWrapper.get({ key }) as UsageByType | null;
|
||||
// only show usage if actor app is the same or if global app ( null appId )
|
||||
const actorAppId = actor.type?.app?.uid
|
||||
if (actorAppId && actorAppId !== appId && appId !== GLOBAL_APP_KEY) {
|
||||
throw new Error('Actor can only get usage details for their own app or global app');
|
||||
}
|
||||
return usage || { total: 0 };
|
||||
})
|
||||
}
|
||||
|
||||
async getCurrentMonthsConsumedCredit(actor: ActorWithType) {
|
||||
if (!actor.type?.user?.uuid) {
|
||||
throw new Error('Actor must be a user to get consumed credits');
|
||||
}
|
||||
const currentMonth = this.#getMonthYearString();
|
||||
// batch get actor usage for the month, and actor policy, and actor policy addons to then compute cost
|
||||
const keys = [
|
||||
`${METRICS_PREFIX}:actor:${actor.type.user.uuid}:${currentMonth}`,
|
||||
`${POLICY_PREFIX}:actor:${actor.type.user.uuid}:addons`,
|
||||
]
|
||||
return this.#superUserService.sudo(async () => {
|
||||
const [usage, addons] = await this.#kvClientWrapper.get({ key: keys }) as [UsageByType | null, PolicyAddOns | null];
|
||||
return usage?.total || 0;
|
||||
})
|
||||
}
|
||||
|
||||
async getActorPolicy(actor: ActorWithType) {
|
||||
async getActorPolicy(actor: ActorWithType): Promise<(keyof typeof SUB_POLICIES) | null> {
|
||||
if (!actor.type?.user.uuid) {
|
||||
throw new Error('Actor must be a user to get policy');
|
||||
}
|
||||
const key = `${POLICY_PREFIX}:actor:${actor.type.user.uuid}`;
|
||||
return this.#superUserService.sudo(async () => {
|
||||
const policy = await this.#kvClientWrapper.get({ key });
|
||||
policy
|
||||
return (policy || 'free') as keyof typeof POLICY_TYPES;
|
||||
return policy as (keyof typeof SUB_POLICIES) || null;
|
||||
})
|
||||
}
|
||||
|
||||
@@ -236,7 +245,7 @@ export class MeteringAndBillingService {
|
||||
})
|
||||
|
||||
}
|
||||
handlePolicyPurchase(actor: ActorWithType, policyType: keyof typeof POLICY_TYPES) {
|
||||
handlePolicyPurchase(actor: ActorWithType, policyType: keyof typeof SUB_POLICIES) {
|
||||
|
||||
|
||||
// TODO DS: this should leverage extensions to call billing implementations
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
import BaseService from '../../BaseService.js';
|
||||
import BaseService from '../BaseService.js';
|
||||
import { MeteringAndBillingService } from "./MeteringService.js";
|
||||
|
||||
export class MeteringAndBillingServiceWrapper extends BaseService {
|
||||
|
||||
/** @type {import('./MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('./MeteringService.js').MeteringAndBillingService} */
|
||||
meteringAndBillingService = undefined;
|
||||
_init() {
|
||||
this.meteringAndBillingService = new MeteringAndBillingService({
|
||||
@@ -0,0 +1,7 @@
|
||||
import { REGISTERED_USER_FREE } from "./registeredUserFreePolicy";
|
||||
import { TEMP_USER_FREE } from "./tempUserFreePolicy";
|
||||
|
||||
export const SUB_POLICIES = {
|
||||
TEMP_USER_FREE,
|
||||
REGISTERED_USER_FREE,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { toMicroCents } from "../utils";
|
||||
|
||||
export const REGISTERED_USER_FREE = {
|
||||
monthUsageAllowence: toMicroCents(0.50),
|
||||
monthlyStorageAllowence: 100 * 1024 * 1024, // 100MiB
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import { toMicroCents } from "../utils";
|
||||
|
||||
export const TEMP_USER_FREE = {
|
||||
monthUsageAllowence: toMicroCents(0.25),
|
||||
monthlyStorageAllowence: 100 * 1024 * 1024, // 100MiB
|
||||
};
|
||||
@@ -5,7 +5,7 @@ import { Context } from "../../../util/context.js";
|
||||
const GLOBAL_APP_KEY = 'global';
|
||||
export class DBKVStore {
|
||||
#db;
|
||||
/** @type {import('../../abuse-prevention/MeteringService/MeteringService').MeteringAndBillingService} */
|
||||
/** @type {import('../../MeteringService/MeteringService.js').MeteringAndBillingService} */
|
||||
#meteringService;
|
||||
|
||||
#global_config = {};
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import * as utils from '../lib/utils.js'
|
||||
import * as utils from '../lib/utils.js';
|
||||
|
||||
class Auth{
|
||||
// Used to generate a unique message id for each message sent to the host environment
|
||||
// we start from 1 because 0 is falsy and we want to avoid that for the message id
|
||||
#messageID = 1;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given authentication token, API origin, and app ID,
|
||||
*
|
||||
@@ -14,7 +13,7 @@ class Auth{
|
||||
* @param {string} APIOrigin - Origin of the API server. Used to build the API endpoint URLs.
|
||||
* @param {string} appID - ID of the app to use.
|
||||
*/
|
||||
constructor (context) {
|
||||
constructor(context) {
|
||||
this.authToken = context.authToken;
|
||||
this.APIOrigin = context.APIOrigin;
|
||||
this.appID = context.appID;
|
||||
@@ -27,22 +26,22 @@ class Auth{
|
||||
* @memberof [Auth]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAuthToken (authToken) {
|
||||
setAuthToken(authToken) {
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the API origin.
|
||||
*
|
||||
*
|
||||
* @param {string} APIOrigin - The new API origin.
|
||||
* @memberof [Auth]
|
||||
* @returns {void}
|
||||
*/
|
||||
setAPIOrigin (APIOrigin) {
|
||||
setAPIOrigin(APIOrigin) {
|
||||
this.APIOrigin = APIOrigin;
|
||||
}
|
||||
|
||||
signIn = (options) =>{
|
||||
|
||||
signIn = (options) => {
|
||||
options = options || {};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -50,17 +49,17 @@ class Auth{
|
||||
let w = 600;
|
||||
let h = 600;
|
||||
let title = 'Puter';
|
||||
var left = (screen.width/2)-(w/2);
|
||||
var top = (screen.height/2)-(h/2);
|
||||
|
||||
var left = (screen.width / 2) - (w / 2);
|
||||
var top = (screen.height / 2) - (h / 2);
|
||||
|
||||
// Store reference to the popup window
|
||||
const popup = window.open(puter.defaultGUIOrigin + '/action/sign-in?embedded_in_popup=true&msg_id=' + msg_id + (window.crossOriginIsolated ? '&cross_origin_isolated=true' : '') +(options.attempt_temp_user_creation ? '&attempt_temp_user_creation=true' : ''),
|
||||
title,
|
||||
'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width='+w+', height='+h+', top='+top+', left='+left);
|
||||
const popup = window.open(`${puter.defaultGUIOrigin}/action/sign-in?embedded_in_popup=true&msg_id=${msg_id}${window.crossOriginIsolated ? '&cross_origin_isolated=true' : ''}${options.attempt_temp_user_creation ? '&attempt_temp_user_creation=true' : ''}`,
|
||||
title,
|
||||
`toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=${w}, height=${h}, top=${top}, left=${left}`);
|
||||
|
||||
// Set up interval to check if popup was closed
|
||||
const checkClosed = setInterval(() => {
|
||||
if (popup.closed) {
|
||||
if ( popup.closed ) {
|
||||
clearInterval(checkClosed);
|
||||
// Remove the message listener
|
||||
window.removeEventListener('message', messageHandler);
|
||||
@@ -69,21 +68,23 @@ class Auth{
|
||||
}, 100);
|
||||
|
||||
function messageHandler(e) {
|
||||
if(e.data.msg_id == msg_id){
|
||||
if ( e.data.msg_id == msg_id ){
|
||||
// Clear the interval since we got a response
|
||||
clearInterval(checkClosed);
|
||||
|
||||
|
||||
// remove redundant attributes
|
||||
delete e.data.msg_id;
|
||||
delete e.data.msg;
|
||||
|
||||
if(e.data.success){
|
||||
if ( e.data.success ){
|
||||
// set the auth token
|
||||
puter.setAuthToken(e.data.token);
|
||||
|
||||
resolve(e.data);
|
||||
}else
|
||||
} else
|
||||
{
|
||||
reject(e.data);
|
||||
}
|
||||
|
||||
// delete the listener
|
||||
window.removeEventListener('message', messageHandler);
|
||||
@@ -92,20 +93,24 @@ class Auth{
|
||||
|
||||
window.addEventListener('message', messageHandler);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
isSignedIn = () =>{
|
||||
if(puter.authToken)
|
||||
isSignedIn = () => {
|
||||
if ( puter.authToken )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getUser = function(...args){
|
||||
let options;
|
||||
|
||||
// If first argument is an object, it's the options
|
||||
if (typeof args[0] === 'object' && args[0] !== null) {
|
||||
if ( typeof args[0] === 'object' && args[0] !== null ) {
|
||||
options = args[0];
|
||||
} else {
|
||||
// Otherwise, we assume separate arguments are provided
|
||||
@@ -122,45 +127,125 @@ class Auth{
|
||||
utils.setupXhrEventHandlers(xhr, options.success, options.error, resolve, reject);
|
||||
|
||||
xhr.send();
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
signOut = () =>{
|
||||
signOut = () => {
|
||||
puter.resetAuthToken();
|
||||
}
|
||||
};
|
||||
|
||||
async whoami () {
|
||||
async whoami() {
|
||||
try {
|
||||
const resp = await fetch(this.APIOrigin + '/whoami', {
|
||||
const resp = await fetch(`${this.APIOrigin}/whoami`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.authToken}`
|
||||
}
|
||||
Authorization: `Bearer ${this.authToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const result = await resp.json();
|
||||
|
||||
|
||||
// Log the response
|
||||
if (globalThis.puter?.apiCallLogger?.isEnabled()) {
|
||||
if ( globalThis.puter?.apiCallLogger?.isEnabled() ) {
|
||||
globalThis.puter.apiCallLogger.logRequest({
|
||||
service: 'auth',
|
||||
operation: 'whoami',
|
||||
params: {},
|
||||
result: result
|
||||
result: result,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
} catch( error ) {
|
||||
// Log the error
|
||||
if (globalThis.puter?.apiCallLogger?.isEnabled()) {
|
||||
if ( globalThis.puter?.apiCallLogger?.isEnabled() ) {
|
||||
globalThis.puter.apiCallLogger.logRequest({
|
||||
service: 'auth',
|
||||
operation: 'whoami',
|
||||
params: {},
|
||||
error: {
|
||||
message: error.message || error.toString(),
|
||||
stack: error.stack
|
||||
}
|
||||
stack: error.stack,
|
||||
},
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getMonthlyUsage() {
|
||||
try {
|
||||
const resp = await fetch(`${this.APIOrigin}/v2/usage`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.authToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await resp.json();
|
||||
|
||||
// Log the response
|
||||
if ( globalThis.puter?.apiCallLogger?.isEnabled() ) {
|
||||
globalThis.puter.apiCallLogger.logRequest({
|
||||
service: 'auth',
|
||||
operation: 'usage',
|
||||
params: {},
|
||||
result: result,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch( error ) {
|
||||
// Log the error
|
||||
if ( globalThis.puter?.apiCallLogger?.isEnabled() ) {
|
||||
globalThis.puter.apiCallLogger.logRequest({
|
||||
service: 'auth',
|
||||
operation: 'usage',
|
||||
params: {},
|
||||
error: {
|
||||
message: error.message || error.toString(),
|
||||
stack: error.stack,
|
||||
},
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getDetailedAppUsage(appId) {
|
||||
if ( !appId ) {
|
||||
throw new Error('appId is required');
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch(`${this.APIOrigin}/v2/usage/${appId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.authToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await resp.json();
|
||||
|
||||
// Log the response
|
||||
if ( globalThis.puter?.apiCallLogger?.isEnabled() ) {
|
||||
globalThis.puter.apiCallLogger.logRequest({
|
||||
service: 'auth',
|
||||
operation: 'detailed_app_usage',
|
||||
params: { appId },
|
||||
result: result,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch( error ) {
|
||||
// Log the error
|
||||
if ( globalThis.puter?.apiCallLogger?.isEnabled() ) {
|
||||
globalThis.puter.apiCallLogger.logRequest({
|
||||
service: 'auth',
|
||||
operation: 'detailed_app_usage',
|
||||
params: { appId },
|
||||
error: {
|
||||
message: error.message || error.toString(),
|
||||
stack: error.stack,
|
||||
},
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
@@ -168,4 +253,4 @@ class Auth{
|
||||
}
|
||||
}
|
||||
|
||||
export default Auth
|
||||
export default Auth;
|
||||
+4
-3
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node10",
|
||||
"module": "node16",
|
||||
"moduleResolution": "node16",
|
||||
"rootDir": ".",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
@@ -15,6 +15,7 @@
|
||||
"**/test/**",
|
||||
"**/tests/**",
|
||||
"node_modules",
|
||||
"dist"
|
||||
"dist",
|
||||
"extensions"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user