javascript-obfuscator
Version:
JavaScript obfuscator
178 lines (147 loc) • 6.69 kB
text/typescript
import { IInitializable } from '../interfaces/IInitializable';
const defaultDescriptor: PropertyDescriptor = {
configurable: true,
enumerable: true
};
const initializedTargetMetadataKey: string = '_initialized';
const initializablePropertiesSetMetadataKey: string = '_initializablePropertiesSet';
const wrappedMethodsSetMetadataKey: string = '_wrappedMethodsSet';
const constructorMethodName: string = 'constructor';
/**
* @param {string} initializeMethodName
* @returns {(target: IInitializable, propertyKey: (string | symbol)) => any}
*/
export function initializable (
initializeMethodName: string = 'initialize'
): (target: IInitializable, propertyKey: string | symbol) => any {
const decoratorName: string = Object.keys(this)[0];
return (target: IInitializable, propertyKey: string | symbol): PropertyDescriptor => {
const initializeMethod: Function = target[initializeMethodName];
if (!initializeMethod || typeof initializeMethod !== 'function') {
throw new Error(`\`${initializeMethodName}\` method with initialization logic not ` +
`found. \`@${decoratorName}\` decorator requires \`${initializeMethodName}\` method`);
}
/**
* Stage #1: initialize target metadata
*/
initializeTargetMetadata(initializedTargetMetadataKey, false, target);
initializeTargetMetadata(initializablePropertiesSetMetadataKey, new Set(), target);
initializeTargetMetadata(wrappedMethodsSetMetadataKey, new Set(), target);
/**
* Stage #2: wrap target methods
*/
wrapTargetMethodsInInitializedCheck(target, initializeMethodName);
wrapInitializeMethodInInitializeCheck(target, initializeMethodName, propertyKey);
/**
* Stage #3: wrap target properties
*/
return wrapInitializableProperty(target, propertyKey);
};
}
/**
* @param {string} metadataKey
* @param metadataValue
* @param {IInitializable} target
*/
function initializeTargetMetadata (metadataKey: string, metadataValue: any, target: IInitializable): void {
const hasInitializedMetadata: boolean = Reflect.hasMetadata(metadataKey, target);
if (!hasInitializedMetadata) {
Reflect.defineMetadata(metadataKey, metadataValue, target);
}
}
/**
* Wraps all target methods with additional logic that check that this methods will called after `initialize` method
*
* @param {IInitializable} target
* @param {string} initializeMethodName
*/
function wrapTargetMethodsInInitializedCheck (target: IInitializable, initializeMethodName: string): void {
const ownPropertyNames: string[] = Object.getOwnPropertyNames(target);
const prohibitedPropertyNames: string[] = [initializeMethodName, constructorMethodName];
ownPropertyNames.forEach((propertyName: string) => {
const initializablePropertiesSet: Set <string | symbol> = Reflect
.getMetadata(initializablePropertiesSetMetadataKey, target);
const wrappedMethodsSet: Set <string | symbol> = Reflect
.getMetadata(wrappedMethodsSetMetadataKey, target);
const isProhibitedPropertyName: boolean = prohibitedPropertyNames.includes(propertyName)
|| initializablePropertiesSet.has(propertyName)
|| wrappedMethodsSet.has(propertyName);
if (isProhibitedPropertyName) {
return;
}
const targetProperty: IInitializable[keyof IInitializable] = target[propertyName];
if (typeof targetProperty !== 'function') {
return;
}
const methodDescriptor: PropertyDescriptor = Object
.getOwnPropertyDescriptor(target, propertyName) ?? defaultDescriptor;
const originalMethod: Function = methodDescriptor.value;
Object.defineProperty(target, propertyName, {
...methodDescriptor,
value (): void {
if (!Reflect.getMetadata(initializedTargetMetadataKey, this)) {
throw new Error(`Class should be initialized with \`${initializeMethodName}()\` method`);
}
return originalMethod.apply(this, arguments);
}
});
wrappedMethodsSet.add(propertyName);
});
}
/**
* Wraps `initialize` method with additional logic to check that `initialized` properties will set
*
* @param {IInitializable} target
* @param {string} initializeMethodName
* @param {string | symbol} propertyKey
*/
function wrapInitializeMethodInInitializeCheck (
target: IInitializable,
initializeMethodName: string,
propertyKey: string | symbol
): void {
const methodDescriptor: PropertyDescriptor = Object
.getOwnPropertyDescriptor(target, initializeMethodName) ?? defaultDescriptor;
const originalMethod: Function = methodDescriptor.value;
Object.defineProperty(target, initializeMethodName, {
...methodDescriptor,
value: function (): typeof originalMethod {
/**
* should define metadata before `initialize` method call,
* because of cases when other methods will called inside `initialize` method
*/
Reflect.defineMetadata(initializedTargetMetadataKey, true, this);
const result: typeof originalMethod = originalMethod.apply(this, arguments);
if (this[propertyKey]) {}
return result;
}
});
}
/**
* Wraps initializable property in additional checks
*
* @param {IInitializable} target
* @param {string | symbol} propertyKey
* @returns {PropertyDescriptor}
*/
function wrapInitializableProperty (target: IInitializable, propertyKey: string | symbol): PropertyDescriptor {
const initializablePropertiesSet: Set <string | symbol> = Reflect
.getMetadata(initializablePropertiesSetMetadataKey, target);
initializablePropertiesSet.add(propertyKey);
const initializablePropertyMetadataKey: string = `_${propertyKey.toString()}`;
const propertyDescriptor: PropertyDescriptor = Object
.getOwnPropertyDescriptor(target, initializablePropertyMetadataKey) ?? defaultDescriptor;
Object.defineProperty(target, propertyKey, {
...propertyDescriptor,
get: function (): any {
if (this[initializablePropertyMetadataKey] === undefined) {
throw new Error(`Property \`${propertyKey.toString()}\` is not initialized! Initialize it first!`);
}
return this[initializablePropertyMetadataKey];
},
set: function (newVal: any): void {
this[initializablePropertyMetadataKey] = newVal;
}
});
return propertyDescriptor;
}