UNPKG

pricing4ts

Version:

![NPM Version](https://img.shields.io/npm/v/pricing4ts) Pricing4TS is a TypeScript-based toolkit designed to enhance the server-side functionality of a pricing-driven SaaS by enabling the seamless integration of pricing plans into the application logic. T

179 lines (178 loc) 8.45 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateUserPricingToken = generateUserPricingToken; exports.addExpressionToToken = addExpressionToToken; exports.extractContextToEvalFromSubscriptionContext = extractContextToEvalFromSubscriptionContext; exports.evaluateFeature = evaluateFeature; var server_1 = require("../server"); var PricingPlanEvaluationError_1 = require("../exceptions/PricingPlanEvaluationError"); var pricing_jwt_utils_1 = require("./pricing-jwt-utils"); function generateUserPricingToken() { var _a; var pricingContext = server_1.PricingContextManager.getContext(); var claims = {}; var subject = 'Default'; var subscriptionContext = pricingContext.getSubscriptionContext(); if (subscriptionContext.username || subscriptionContext.user) { subject = String((_a = subscriptionContext.username) !== null && _a !== void 0 ? _a : subscriptionContext.user); } claims.sub = subject; try { claims.subscriptionContext = subscriptionContext; } catch (e) { throw new PricingPlanEvaluationError_1.PricingPlanEvaluationError('Error while retrieving user context! Please check your PricingContext.getSubscriptionContext() method'); } if (!pricingContext.userAffectedByPricing()) { return pricing_jwt_utils_1.PricingJwtUtils.encodeToken(claims); } var configuration = pricingContext.getPlanContext(); var featureStatuses = computeFeatureStatuses(configuration, subscriptionContext); claims.features = featureStatuses; claims.pricingContext = configuration; var token = pricing_jwt_utils_1.PricingJwtUtils.encodeToken(claims); return token; } /** * Modifies the given JWT by changing the evaluation of the specified feature * using a {@link string} expression that will be evaluated on the client side * of the application. * * @param token The generated JWT returned by the * {@link generateUserPricingToken()} method. * @param featureId The ID of a feature defined within the token body. * @param expression The expression for the feature that will replace its * current evaluation. * @returns A modified version of the provided JWT containing the new expression * in the "eval" attribute of the specified feature. */ function addExpressionToToken(token, featureId, expression) { var tokenFeatures = pricing_jwt_utils_1.PricingJwtUtils.getFeaturesFromJwtToken(token); try { tokenFeatures[featureId].eval = expression; } catch (e) { console.error('[ERROR] Feature not found while trying to add expression to token!'); } return pricing_jwt_utils_1.PricingJwtUtils.updateTokenFeatures(token, tokenFeatures); } function computeFeatureStatuses(configuration, subscriptionContext) { var featureStatuses = {}; var pricingContext = extractContextToEvalFromSubscriptionContext(configuration); // This is defined in order to perform the "eval" for (var _i = 0, _a = Object.values(configuration.features); _i < _a.length; _i++) { var feature = _a[_i]; var _b = evaluateFeature(feature, configuration, subscriptionContext, pricingContext), error = _b.error, featureStatusWithoutError = __rest(_b, ["error"]); featureStatuses[feature.name] = featureStatusWithoutError; } return featureStatuses; } function extractContextToEvalFromSubscriptionContext(subscriptionContext) { var _a, _b; var subscriptionContextFeatures = subscriptionContext.features; var subscriptionContextUsageLimits = subscriptionContext.usageLimits; var contextToEval = { features: {}, usageLimits: {} }; for (var _i = 0, _c = Object.values(subscriptionContextFeatures); _i < _c.length; _i++) { var feature = _c[_i]; contextToEval.features[feature.name] = (_a = feature.value) !== null && _a !== void 0 ? _a : feature.defaultValue; } for (var _d = 0, _e = Object.values(subscriptionContextUsageLimits); _d < _e.length; _d++) { var usageLimit = _e[_d]; contextToEval.usageLimits[usageLimit.name] = (_b = usageLimit.value) !== null && _b !== void 0 ? _b : usageLimit.defaultValue; } return contextToEval; } function evaluateFeature(feature, configuration, subscriptionContext, pricingContext) { var _a, _b, _c; if (configuration === void 0) { configuration = undefined; } if (subscriptionContext === void 0) { subscriptionContext = undefined; } if (pricingContext === void 0) { pricingContext = undefined; } var evaluationContext = server_1.PricingContextManager.getContext(); if (typeof feature === 'string') { feature = evaluationContext.getPricing().features[feature]; if (!feature) { console.warn("[WARNING] Feature ".concat(feature, " not found!")); return { eval: false, used: null, limit: null, error: { code: 'FLAG_NOT_FOUND', message: "Feature ".concat(feature, " is not present in the pricing"), } }; } } var featureExpression = (_a = feature.serverExpression) !== null && _a !== void 0 ? _a : feature.expression; if (!featureExpression) { console.warn("[WARNING] Feature ".concat(feature.name, " has no expression defined!")); return { eval: false, used: null, limit: null, error: { code: 'PARSE_ERROR', message: "Feature ".concat(feature.name, " has no expression defined!"), } }; } else { if (!configuration) { configuration = evaluationContext.getPlanContext(); } if (!subscriptionContext) { subscriptionContext = evaluationContext.getSubscriptionContext(); } if (!pricingContext) { pricingContext = extractContextToEvalFromSubscriptionContext(configuration); } var evalResult = eval(featureExpression); if (typeof evalResult !== 'boolean') { console.warn("[WARNING] Feature ".concat(feature.name, " has an expression that does not return a boolean!")); return { eval: false, used: null, limit: null, error: { code: 'TYPE_MISMATCH', message: "Feature ".concat(feature.name, " has an expression that does not return a boolean!"), } }; } else { if (evalResult !== null && evalResult !== undefined) { var numericLimitsOfSelectedFeature = Object.values(configuration.usageLimits).filter(function (u) { var _a; return ((_a = u.linkedFeatures) === null || _a === void 0 ? void 0 : _a.includes(feature.name)) && u.valueType === 'NUMERIC'; }); var shouldLimitAppearInToken = numericLimitsOfSelectedFeature.length === 1; return { eval: evalResult, used: shouldLimitAppearInToken ? (_b = subscriptionContext[feature.name]) !== null && _b !== void 0 ? _b : null : null, limit: shouldLimitAppearInToken ? (_c = numericLimitsOfSelectedFeature[0].value) !== null && _c !== void 0 ? _c : numericLimitsOfSelectedFeature[0].defaultValue : null, error: null, }; } else { return { eval: false, used: null, limit: null, error: { code: 'GENERAL', message: "Error while evaluating expression for feature ".concat(feature.name, ". The returned expression is null or undefined"), } }; } } } }