pricing4ts
Version:
 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
JavaScript
;
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"),
}
};
}
}
}
}