Home Reference Source

src/values/SmartLinkValue.js

'use strict';
/* @flow */

const Value = require('../Value');
const LinkValue = require('./LinkValue');
const CompletionRecord = require('../CompletionRecord');
const ArrayValue = require('./ArrayValue');
const EvaluatorInstruction = require('../EvaluatorInstruction');
/**
 * Represents a value that maps directly to an untrusted local value.
 */

let privilegedThreads = new WeakSet();

class SmartLinkValue extends LinkValue {

	constructor(value, realm) {
		super(value, realm);
	}

	allowRead(name, e) {
		if ( e && privilegedThreads.has(e) ) return true;
		//if ( name === 'call' ) return true;
		//return true;
		if ( name.indexOf('esper_') === 0 ) return true;
		if ( name === 'hasOwnProperty' ) return true;
		let props = this.apiProperties;
		if ( props === null ) return true;
		return props.indexOf(name) !== -1;
	}

	allowWrite(name, e) {
		if ( e && privilegedThreads.has(e) ) return true;
		if ( name.indexOf('esper_') === 0 ) name = name.substr(6);
		var allowed = [];
		var native = this.native;
		if ( native.apiUserProperties ) {
			Array.prototype.push.apply(allowed, native.apiUserProperties);
		}

		return allowed.indexOf(name) != -1;
	}

	getPropertyValueMap() {
		let list  = {};
		for ( let p in this.native ) {
			let v = this.native[p];
			if ( this.allowRead(p) ) {
				list[p] = this.makeLink(v);
			}
		}
		return list;
	}

	static make(native, realm) {
		let wellKnown = realm.lookupWellKnown(native);
		if ( wellKnown ) return wellKnown;

		if ( Array.isArray(native) ) {
			var ia = new Array(native.length);
			for ( let i = 0; i < native.length; ++i ) {
				ia[i] = realm.import(native[i], 'smart');
			}
			return ArrayValue.make(ia, realm);
		}

		return new SmartLinkValue(native, realm);
	}

	makeLink(value) {
		return this.realm.import(value, 'smart');
	}


	ref(name, s) {
		let native = this.native;
		let owner = this;
		if ( ('esper_' + name) in native ) name = 'esper_' + name;

		return super.ref(name, s);
	}

	*set(name, value, s) {
		let evaluator = yield EvaluatorInstruction.getEvaluator;
		let native = this.native;
		if ( name in this.native ) {
			if ( !this.allowWrite(name, evaluator) ) return yield CompletionRecord.makeTypeError(s.realm, "Can't write to protected property: " + name);
		} else {
			if ( !native.apiUserProperties ) native.apiUserProperties = [];

			if ( native.apiUserProperties.indexOf(name) == -1 ) {
				native.apiUserProperties.push(name);
			}
		}

		return yield * super.set(name, value, s);

	}

	*get(name, realm, origional) {
		let evaluator = yield EvaluatorInstruction.getEvaluator;
		let native = this.native;
		if ( ('esper_' + name) in this.native ) name = 'esper_' + name;

		if ( !(name in native) ) {
			return Value.undef;
		}

		if ( !this.allowRead(name, evaluator) ) {
			return yield CompletionRecord.makeTypeError(realm, "Can't read protected property: " + name);
		}

		return yield * super.get(name, realm, origional);
	}

	get apiProperties() {
		let allowed = [];
		let native = this.native;

		if ( native.apiProperties === undefined && native.apiMethods === undefined ) return null;

		if ( native.apiProperties ) {
			Array.prototype.push.apply(allowed, native.apiProperties);
		}

		if ( native.apiUserProperties ) {
			Array.prototype.push.apply(allowed, native.apiUserProperties);
		}

		if ( native.apiMethods ) {
			Array.prototype.push.apply(allowed, native.apiMethods);
		}


		if ( native.apiOwnMethods ) {
			Array.prototype.push.apply(allowed, native.apiOwnMethods);
		}


		if ( native.programmableProperties ) {
			Array.prototype.push.apply(allowed, native.programmableProperties);
		}

		return allowed;
	}

	get debugString() {
		let props = this.apiProperties;
		return '[SmartLink: ' + this.native + ', props: ' + (props ? props.join(',') : '[none]') + ']';
	}

}

SmartLinkValue.makeThreadPrivileged = function(e) {
	privilegedThreads.add(e);
};

SmartLinkValue.isThreadPrivileged = function(e) {
	return privilegedThreads.has(e);
};

SmartLinkValue.makeThreadPrivlaged = SmartLinkValue.makeThreadPrivileged;

module.exports = SmartLinkValue;