<commit_before>import React from 'react';
import PropTypes from 'prop-types';

import fetchWcl from 'common/fetchWclApi';
import SPELLS from 'common/SPELLS';
import ManaStyles from 'interface/others/ManaStyles';

import PainComponent from './PainComponent';
import PainChart from './PainChart';

class Pain extends React.PureComponent {
  static propTypes = {
    reportCode: PropTypes.string.isRequired,
    actorId: PropTypes.number.isRequired,
    start: PropTypes.number.isRequired,
    end: PropTypes.number.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      pain: null,
      bossHealth: null,
    };
  }

  componentDidMount() {
    this.loadData();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.reportCode !== this.props.reportCode || prevProps.actorId !== this.props.actorId || prevProps.start !== this.props.start || prevProps.end !== this.props.end) {
      this.loadData();
    }
  }

  loadData() {
    this.load(this.props.reportCode, this.props.actorId, this.props.start, this.props.end);
  }

  load(reportCode, actorId, start, end) {
    const painPromise = fetchWcl(`report/tables/resources/${reportCode}`, {
      start,
      end,
      sourceid: actorId,
      abilityid: 118,
    })
      .then(json => {
        this.setState({
          pain: json,
        });
      });

    const bossHealthPromise = fetchWcl(`report/tables/resources/${reportCode}`, {
      start,
      end,
      sourceclass: 'Boss',
      hostility: 1,
      abilityid: 1000,
    })
      .then(json => {
        this.setState({
          bossHealth: json,
        });
      });

    return Promise.all([painPromise, bossHealthPromise]);
  }

  get plot() {
    const { start, end } = this.props;

    const pain = this.state.pain.series[0].data.map(([timestamp, amount]) => {
      const x = Math.max(timestamp, start);
      return {
        x,
        y: amount / 10,
      };
    });

    const wasted = this.state.pain.series[0].events
      .filter(event => event.waste !== undefined)
      .map(({ timestamp, waste }) => ({ x: timestamp, y: waste }));

    const bossData = this.state.bossHealth.series.map((series, i) => {
      const data = series.data.map(([timestamp, health]) => ({ x: timestamp, y: health }));
      return {
        title: `${series.name} Health`,
        borderColor: ManaStyles[`Boss-${i}`].borderColor,
        backgroundColor: ManaStyles[`Boss-${i}`].backgroundColor,
        data,
      };
    });

    return (
      <PainChart
        pain={pain}
        wasted={wasted}
        bossData={bossData}
        startTime={start}
        endTime={end}
      />
    );
  }

  render() {
    if (!this.state.pain || !this.state.bossHealth) {
      return (

          Loading...

      );
    }

    if(!this.state.pain.series[0]) {
      return (

          This pain chart data from Warcraft Logs is corrupted and it cannot be parsed.

      );
    }

    const { start } = this.props;
    const painBySecond = {
      0: 0,
    };
    this.state.pain.series[0].data.forEach((item) => {
      const secIntoFight = Math.floor((item[0] - start) / 1000);
      painBySecond[secIntoFight] = item[1];
    });

    const abilitiesAll = {};
    const categories = {
      generated: 'Pain Generators',
      spent: 'Pain Spenders',
    };

    let lastSecFight = start;
    this.state.pain.series[0].events.forEach((event) => {
      const secIntoFight = Math.floor((event.timestamp - start) / 1000);
      if (event.type === 'cast') {
        const spell = SPELLS[event.ability.guid];
        if (!abilitiesAll[`${event.ability.guid}_spend`]) {
          abilitiesAll[`${event.ability.guid}_spend`] = {
            ability: {
              category: 'Pain Spenders',
              name: (spell === undefined) ? event.ability.name : spell.name,
              spellId: event.ability.guid,
            },
            spent: 0,
            casts: 0,
            created: 0,
            wasted: 0,
          };
        }
        abilitiesAll[`${event.ability.guid}_spend`].casts += 1;
        const lastPain = lastSecFight === secIntoFight ? painBySecond[lastSecFight - 1] : painBySecond[lastSecFight];
        const spendResource = spell !== undefined ? ((spell.painCost !== undefined) ? spell.painCost : (spell.max_pain < lastPain ? spell.max_pain : lastPain)) : 0;
        abilitiesAll[`${event.ability.guid}_spend`].spent += spendResource;
        abilitiesAll[`${event.ability.guid}_spend`].wasted += spell.max_pain !== undefined ? spell.max_pain - spendResource : 0;
      } else if (event.type === 'energize') {
        if (!abilitiesAll[`${event.ability.guid}_gen`]) {
          const spell = SPELLS[event.ability.guid];
          abilitiesAll[`${event.ability.guid}_gen`] = {
            ability: {
              category: 'Pain Generators',
              name: (spell === undefined) ? event.ability.name : spell.name,
              spellId: event.ability.guid,
            },
            spent: 0,
            casts: 0,
            created: 0,
            wasted: 0,
          };
        }
        abilitiesAll[`${event.ability.guid}_gen`].casts += 1;
        abilitiesAll[`${event.ability.guid}_gen`].created += event.resourceChange;
        abilitiesAll[`${event.ability.guid}_gen`].wasted += event.waste;
      }
      if (secIntoFight !== lastSecFight) {
        lastSecFight = secIntoFight;
      }
    });

    const abilities = Object.keys(abilitiesAll).map(key => abilitiesAll[key]);
    abilities.sort((a, b) => {
      if (a.created < b.created) {
        return 1;
      } else if (a.created === b.created) {
        return 0;
      }
      return -1;
    });

    return (
      <>
        {this.plot}
        <PainComponent
          abilities={abilities}
          categories={categories}
        />
      </>
    );
  }
}

export default Pain;

<commit_msg>Revert "This is a more descriptive name"<commit_after>import React from 'react';
import PropTypes from 'prop-types';

import fetchWcl from 'common/fetchWclApi';
import SPELLS from 'common/SPELLS';
import ManaStyles from 'interface/others/ManaStyles';

import PainComponent from './PainComponent';
import PainChart from './PainChart';

class Pain extends React.PureComponent {
  static propTypes = {
    reportCode: PropTypes.string.isRequired,
    actorId: PropTypes.number.isRequired,
    start: PropTypes.number.isRequired,
    end: PropTypes.number.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      pain: null,
      bossHealth: null,
    };
  }

  componentDidMount() {
    this.update();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.reportCode !== this.props.reportCode || prevProps.actorId !== this.props.actorId || prevProps.start !== this.props.start || prevProps.end !== this.props.end) {
      this.update();
    }
  }

  update() {
    this.load(this.props.reportCode, this.props.actorId, this.props.start, this.props.end);
  }

  load(reportCode, actorId, start, end) {
    const painPromise = fetchWcl(`report/tables/resources/${reportCode}`, {
      start,
      end,
      sourceid: actorId,
      abilityid: 118,
    })
      .then(json => {
        this.setState({
          pain: json,
        });
      });

    const bossHealthPromise = fetchWcl(`report/tables/resources/${reportCode}`, {
      start,
      end,
      sourceclass: 'Boss',
      hostility: 1,
      abilityid: 1000,
    })
      .then(json => {
        this.setState({
          bossHealth: json,
        });
      });

    return Promise.all([painPromise, bossHealthPromise]);
  }

  get plot() {
    const { start, end } = this.props;

    const pain = this.state.pain.series[0].data.map(([timestamp, amount]) => {
      const x = Math.max(timestamp, start);
      return {
        x,
        y: amount / 10,
      };
    });

    const wasted = this.state.pain.series[0].events
      .filter(event => event.waste !== undefined)
      .map(({ timestamp, waste }) => ({ x: timestamp, y: waste }));

    const bossData = this.state.bossHealth.series.map((series, i) => {
      const data = series.data.map(([timestamp, health]) => ({ x: timestamp, y: health }));
      return {
        title: `${series.name} Health`,
        borderColor: ManaStyles[`Boss-${i}`].borderColor,
        backgroundColor: ManaStyles[`Boss-${i}`].backgroundColor,
        data,
      };
    });

    return (
      <PainChart
        pain={pain}
        wasted={wasted}
        bossData={bossData}
        startTime={start}
        endTime={end}
      />
    );
  }

  render() {
    if (!this.state.pain || !this.state.bossHealth) {
      return (

          Loading...

      );
    }

    if(!this.state.pain.series[0]) {
      return (

          This pain chart data from Warcraft Logs is corrupted and it cannot be parsed.

      );
    }

    const { start } = this.props;
    const painBySecond = {
      0: 0,
    };
    this.state.pain.series[0].data.forEach((item) => {
      const secIntoFight = Math.floor((item[0] - start) / 1000);
      painBySecond[secIntoFight] = item[1];
    });

    const abilitiesAll = {};
    const categories = {
      generated: 'Pain Generators',
      spent: 'Pain Spenders',
    };

    let lastSecFight = start;
    this.state.pain.series[0].events.forEach((event) => {
      const secIntoFight = Math.floor((event.timestamp - start) / 1000);
      if (event.type === 'cast') {
        const spell = SPELLS[event.ability.guid];
        if (!abilitiesAll[`${event.ability.guid}_spend`]) {
          abilitiesAll[`${event.ability.guid}_spend`] = {
            ability: {
              category: 'Pain Spenders',
              name: (spell === undefined) ? event.ability.name : spell.name,
              spellId: event.ability.guid,
            },
            spent: 0,
            casts: 0,
            created: 0,
            wasted: 0,
          };
        }
        abilitiesAll[`${event.ability.guid}_spend`].casts += 1;
        const lastPain = lastSecFight === secIntoFight ? painBySecond[lastSecFight - 1] : painBySecond[lastSecFight];
        const spendResource = spell !== undefined ? ((spell.painCost !== undefined) ? spell.painCost : (spell.max_pain < lastPain ? spell.max_pain : lastPain)) : 0;
        abilitiesAll[`${event.ability.guid}_spend`].spent += spendResource;
        abilitiesAll[`${event.ability.guid}_spend`].wasted += spell.max_pain !== undefined ? spell.max_pain - spendResource : 0;
      } else if (event.type === 'energize') {
        if (!abilitiesAll[`${event.ability.guid}_gen`]) {
          const spell = SPELLS[event.ability.guid];
          abilitiesAll[`${event.ability.guid}_gen`] = {
            ability: {
              category: 'Pain Generators',
              name: (spell === undefined) ? event.ability.name : spell.name,
              spellId: event.ability.guid,
            },
            spent: 0,
            casts: 0,
            created: 0,
            wasted: 0,
          };
        }
        abilitiesAll[`${event.ability.guid}_gen`].casts += 1;
        abilitiesAll[`${event.ability.guid}_gen`].created += event.resourceChange;
        abilitiesAll[`${event.ability.guid}_gen`].wasted += event.waste;
      }
      if (secIntoFight !== lastSecFight) {
        lastSecFight = secIntoFight;
      }
    });

    const abilities = Object.keys(abilitiesAll).map(key => abilitiesAll[key]);
    abilities.sort((a, b) => {
      if (a.created < b.created) {
        return 1;
      } else if (a.created === b.created) {
        return 0;
      }
      return -1;
    });

    return (
      <>
        {this.plot}
        <PainComponent
          abilities={abilities}
          categories={categories}
        />
      </>
    );
  }
}

export default Pain;

<|endoftext|>


<commit_before>describe('baseManager', function () {
	const baseManager = require('../../app/chessbase/base-manager');
	let base = baseManager.getBase();

	describe('addToBase', function () {
		beforeAll(function() {
			spyOn(baseManager, 'saveBase').and.stub();
		})
        it('add number to base', function () {
            base.s = []
            baseManager.addToBase(['d4'], 'Nf6', 0.12, 30)
            expect(base.s[0]).toEqual({m: 'd4', n: 1, c: 'w', e: { v: 0.12, d: 30 }, s: [
                        { m: 'Nf6', n: 1, c: 'b', e: { v: 0.12, d: 30 } }
            ]})
        })
        it('updates best answer evaluation data', function () {
            base.s = []
            baseManager.addToBase([], 'd4', 0.12, 30)
            expect(base.s[0].e).toEqual({v: 0.12, d: 30})
        })
        it('change best answer when it is considered previously as not best', function() {
		    base.s = [{m: 'd4', s: [{m: 'c5', s: []}]}]
		    baseManager.addToBase(['d4'], 'e6', 0.12, 30)
			expect(base.s[0].s[0].m).toEqual('e6')
			expect(base.s[0].s[1].m).toEqual('c5')
		})
		it('updates position evaluation data', function() {
			base.s = [{m: 'd4', e: {v: 0.12, d: 29}}]
		    baseManager.addToBase(['d4'], 'e6', 0.23, 34)
			expect(base.s[0].e).toEqual({v: 0.23, d: 34})
		})
		it('main line is not modified', function() {
		    base.s = [{m: 'd4'}]
			baseManager.addToBase(['e4'], 'e6', 0.12, 34)
		    expect(base.s[0].m).toEqual('d4')
		})
		it('promote answer to the top if it becomes the best', function() {
			base.s = [{m: 'e4'},{m:'d4'}]
			baseManager.addToBase([],'d4',0.1,40)
			expect(base.s.length).toEqual(2)
			expect(base.s[0].m).toEqual('d4')
		})
		it('does not update evaluation if it is lower than existent', function() {
			base.e = {v: 0.12, d: 40}
			baseManager.addToBase([], 'd4', 0.1, 39)
			expect(base.e).toEqual({v: 0.12, d: 40})
		})
		it('does not update best answer evaluation if it is lower than existent', function() {
			base.s = [{m: 'd4', e: {v: 0.11, d: 40}}]
			baseManager.addToBase([], 'd4', 0.1, 39)
			expect(base.s[0].e).toEqual({v: 0.11, d: 40})
		})
	});
	describe('saveBase', () => {
		it('save to base.json', function() {
			fs = require('fs')
			spyOn(fs, 'writeFile').and.stub();
			delete base.e
			delete base.s
			baseManager.saveBase()
			expect(fs.writeFile).toHaveBeenCalledWith('base.json', '{"m": "", "n": 0, "c": "b", "t": "wb", "s": []}', jasmine.anything());
		})
    })
})
<commit_msg>additional tests for readBase and saveBase<commit_after>describe('baseManager', function () {
	const baseManager = require('../../app/chessbase/base-manager');
	let base = baseManager.getBase();

	describe('addToBase', function () {
		beforeAll(function() {
			spyOn(baseManager, 'saveBase').and.stub();
		})
        it('add number to base', function () {
            base.s = []
            baseManager.addToBase(['d4'], 'Nf6', 0.12, 30)
            expect(base.s[0]).toEqual({m: 'd4', n: 1, c: 'w', e: { v: 0.12, d: 30 }, s: [
                        { m: 'Nf6', n: 1, c: 'b', e: { v: 0.12, d: 30 } }
            ]})
        })
        it('updates best answer evaluation data', function () {
            base.s = []
            baseManager.addToBase([], 'd4', 0.12, 30)
            expect(base.s[0].e).toEqual({v: 0.12, d: 30})
        })
        it('change best answer when it is considered previously as not best', function() {
		    base.s = [{m: 'd4', s: [{m: 'c5', s: []}]}]
		    baseManager.addToBase(['d4'], 'e6', 0.12, 30)
			expect(base.s[0].s[0].m).toEqual('e6')
			expect(base.s[0].s[1].m).toEqual('c5')
		})
		it('updates position evaluation data', function() {
			base.s = [{m: 'd4', e: {v: 0.12, d: 29}}]
		    baseManager.addToBase(['d4'], 'e6', 0.23, 34)
			expect(base.s[0].e).toEqual({v: 0.23, d: 34})
		})
		it('main line is not modified', function() {
		    base.s = [{m: 'd4'}]
			baseManager.addToBase(['e4'], 'e6', 0.12, 34)
		    expect(base.s[0].m).toEqual('d4')
		})
		it('promote answer to the top if it becomes the best', function() {
			base.s = [{m: 'e4'},{m:'d4'}]
			baseManager.addToBase([],'d4',0.1,40)
			expect(base.s.length).toEqual(2)
			expect(base.s[0].m).toEqual('d4')
		})
		it('does not update evaluation if it is lower than existent', function() {
			base.e = {v: 0.12, d: 40}
			baseManager.addToBase([], 'd4', 0.1, 39)
			expect(base.e).toEqual({v: 0.12, d: 40})
		})
		it('does not update best answer evaluation if it is lower than existent', function() {
			base.s = [{m: 'd4', e: {v: 0.11, d: 40}}]
			baseManager.addToBase([], 'd4', 0.1, 39)
			expect(base.s[0].e).toEqual({v: 0.11, d: 40})
		})
	});
	describe('saveBase', () => {
		it('save to base.json', function() {
			fs = require('fs')
			spyOn(fs, 'writeFile').and.stub();
			delete base.e
			delete base.s
			baseManager.saveBase()
			expect(fs.writeFile).toHaveBeenCalledWith('base.json', '{"m": "", "n": 0, "c": "b", "t": "wb", "s": []}', jasmine.anything());
		});
		it('console.error is called in case of error during writeFile', () => {
			fs = require('fs');
			spyOn(fs, 'writeFile').and.callFake((file, data, callback) => {callback('error')});
			spyOn(console, 'error').and.callThrough();

			baseManager.saveBase();

			expect(console.error).toHaveBeenCalledWith('error');
		});
		it('console.error is not called in case of no error during writeFile', () => {
			fs = require('fs');
			spyOn(fs, 'writeFile').and.callFake((file, data, callback) => {callback()});
			spyOn(console, 'error').and.callThrough();

			baseManager.saveBase();

			expect(console.error).not.toHaveBeenCalled();
		});
	});
	describe('readBase', () => {
		it('reads base from base.json', () => {
			fs = require('fs');
			spyOn(fs, 'readFileSync').and.stub();

			baseManager.readBase();

			expect(fs.readFileSync).toHaveBeenCalledWith('base.json');
		});
		it('logs error when file cound not be read', () => {
			fs = require('fs');
			spyOn(fs, 'readFileSync').and.throwError('could not read');
			spyOn(console, 'error').and.callThrough();

			baseManager.readBase();

			expect(console.error).toHaveBeenCalled();
		})
	});
})
<|endoftext|>


<commit_before>import xmldom from 'xmldom'
import linksRe from 'app/utils/Links'
import {validate_account_name} from 'app/utils/ChainValidation'

const noop = () => {}
const DOMParser = new xmldom.DOMParser({
    errorHandler: {warning: noop, error: noop}
})
const XMLSerializer = new xmldom.XMLSerializer()

/**
 * Functions performed by HTMLReady
 *
 * State reporting
 *  - hashtags: collect all #tags in content
 *  - usertags: collect all @mentions in content
 *  - htmltags: collect all html <tags> used (for validation)
 *  - images: collect all image URLs in content
 *  - links: collect all href URLs in content
 *
 * Mutations
 *  - link()
 *    - ensure all  href's begin with a protocol. prepend https:// otherwise.
 *  - iframe()
 *    - wrap all s in
 for responsive sizing
 *  - img()
 *    - convert any  src IPFS prefixes to standard URL
 *    - change relative protocol to https://
 *  - linkifyNode()
 *    - scans text content to be turned into rich content
 *    - embedYouTubeNode()
 *      - identify plain youtube URLs and prep them for "rich embed"
 *    - linkify()
 *      - scan text for:
 *        - #tags, convert to  links
 *        - @mentions, convert to  links
 *        - naked URLs
 *          - if img URL, normalize URL and convert to  tag
 *          - otherwise, normalize URL and convert to  link
 *  - proxifyImages()
 *    - prepend proxy URL to any non-local  src's
 *
 * We could implement 2 levels of HTML mutation for maximum reuse:
 *  1. Normalization of HTML - non-proprietary, pre-rendering cleanup/normalization
 *    - (state reporting done at this level)
 *    - normalize URL protocols
 *    - convert naked URLs to images/links
 *    - convert embeddable URLs to s
 *    - basic sanitization?
 *  2. Steemit.com Rendering - add in proprietary Steemit.com functions/links
 *    - convert s to custom objects
 *    - linkify #tags and @mentions
 *    - proxify images
 *
 */

/** Split the HTML on top-level elements. This allows react to compare separately, preventing excessive re-rendering.
 * Used in MarkdownViewer.jsx
 */
// export function sectionHtml (html) {
//   const doc = DOMParser.parseFromString(html, 'text/html')
//   const sections = Array(...doc.childNodes).map(child => XMLSerializer.serializeToString(child))
//   return sections
// }

/** Embed videos, link mentions and hashtags, etc...
*/
export default function (html, {mutate = true} = {}) {
    const state = {mutate}
    state.hashtags = new Set()
    state.usertags = new Set()
    state.htmltags = new Set()
    state.images = new Set()
    state.links = new Set()
    try {
        const doc = DOMParser.parseFromString(html, 'text/html')
        traverse(doc, state)
        if(mutate) proxifyImages(doc)
        // console.log('state', state)
        if(!mutate) return state
        return {html: (doc) ? XMLSerializer.serializeToString(doc) : '', ...state}
    }catch(error) {
        // Not Used, parseFromString might throw an error in the future
        console.error(error.toString())
        return {html}
    }
}

function traverse(node, state, depth = 0) {
    if(!node || !node.childNodes) return
    Array(...node.childNodes).forEach(child => {
        // console.log(depth, 'child.tag,data', child.tagName, child.data)
        const tag = child.tagName ? child.tagName.toLowerCase() : null
        if(tag) state.htmltags.add(tag)

        if(tag === 'img')
            img(state, child)
        else if(tag === 'iframe')
            iframe(state, child)
        else if(tag === 'a')
            link(state, child)
        else if(child.nodeName === '#text')
            linkifyNode(child, state)

        traverse(child, state, depth + 1)
    })
}

function link(state, child) {
    const url = child.getAttribute('href')
    if(url) {
        state.links.add(url)
        if(state.mutate) {
            // If this link is not relative, http, or https -- add https.
            if(! /^\/(?!\/)|(https?:)?\/\//.test(url)) {
                child.setAttribute('href', "https://"+url)
            }
        }
    }
}

// wrap iframes in div.videoWrapper to control size/aspect ratio
function iframe(state, child) {
    const {mutate} = state
    if(!mutate) return

    const tag = child.parentNode.tagName ? child.parentNode.tagName.toLowerCase() : child.parentNode.tagName
    if(tag == 'div' && child.parentNode.getAttribute('class') == 'videoWrapper') return;
    const html = XMLSerializer.serializeToString(child)
    child.parentNode.replaceChild(DOMParser.parseFromString(`<div class="videoWrapper">${html}</div>`), child)
}

function img(state, child) {
    const url = child.getAttribute('src')
    if(url) {
        state.images.add(url)
        if(state.mutate) {
            let url2 = ipfsPrefix(url)
            if(/^\/\//.test(url2)) {
                // Change relative protocol imgs to https
                url2 = "https:" + url2
            }
            if(url2 !== url) {
                child.setAttribute('src', url2)
            }
        }
    }
}

// For all img elements with non-local URLs, prepend the proxy URL (e.g. `https://img0.steemit.com/0x0/`)
function proxifyImages(doc) {
    if (!$STM_Config.img_proxy_prefix) return
    if (!doc) return;
    [...doc.getElementsByTagName('img')].forEach(node => {
        const url = node.getAttribute('src')
        if(! linksRe.local.test(url))
            node.setAttribute('src', $STM_Config.img_proxy_prefix + '0x0/' + url)
    })
}

function linkifyNode(child, state) {try{
    const tag = child.parentNode.tagName ? child.parentNode.tagName.toLowerCase() : child.parentNode.tagName
    if(tag === 'code') return
    if(tag === 'a') return

    const {mutate} = state
    if(!child.data) return
    if(embedYouTubeNode(child, state.links, state.images)) return
    if(embedVimeoNode(child, state.links, state.images)) return

    const data = XMLSerializer.serializeToString(child)
    const content = linkify(data, state.mutate, state.hashtags, state.usertags, state.images, state.links)
    if(mutate && content !== data) {
        child.parentNode.replaceChild(DOMParser.parseFromString(`<span>${content}</span>`), child)
    }
} catch(error) {console.log(error)}}

function linkify(content, mutate, hashtags, usertags, images, links) {
    // hashtag
    content = content.replace(/(^|\s)(#[-a-z\d]+)/ig, tag => {
        if(/#[\d]+$/.test(tag)) return tag // Don't allow numbers to be tags
        const space = /^\s/.test(tag) ? tag[0] : ''
        const tag2 = tag.trim().substring(1)
        const tagLower = tag2.toLowerCase()
        if(hashtags) hashtags.add(tagLower)
        if(!mutate) return tag
        return space + `<a href="/trending/${tagLower}">${tag}</a>`
    })

    // usertag (mention)
    content = content.replace(/(^|[^\w]|\s)@([a-z][-\.a-z\d]+[a-z\d])/ig, (match, symbol, user) => {
        // console.log('['+match+']', '['+symbol+']', '['+user+']')
        const userLower = user.toLowerCase()
        const valid = validate_account_name(userLower) == null
        if(valid && usertags) usertags.add(userLower)
        if(!mutate) return match
        return valid ? `${symbol}<a href="/@${userLower}">@${user}</a>` : symbol + '@' + user
    })

    content = content.replace(linksRe.any, ln => {
        if(linksRe.image.test(ln)) {
            if(images) images.add(ln)
            return `<img src="${ipfsPrefix(ln)}" />`
        }
        if(links) links.add(ln)
        return `<a href="${ipfsPrefix(ln)}">${ln}</a>`
    })
    return content
}

function embedYouTubeNode(child, links, images) {try{
    if(!child.data) return false
    const data = child.data

    let url
    {
        const m = data.match(linksRe.youTube)
        url = m ? m[0] : null
    }
    if(!url) return false;

    let id
    {
        const m = url.match(linksRe.youTubeId)
        id = m && m.length >=2 ? m[1] : null
    }
    if(!id) return false

    const v = DOMParser.parseFromString(`~~~ embed:${id} youtube ~~~`)
    child.parentNode.replaceChild(v, child)
    if(links) links.add(url)
    if(images) images.add('https://img.youtube.com/vi/' + id + '/0.jpg')
    return true
} catch(error) {console.log(error); return false}}

function embedVimeoNode(child, links, /*images*/) {try{
    if(!child.data) return false
    const data = child.data

    let id
    {
        const m = data.match(linksRe.vimeoId)
        id = m && m.length >= 2 ? m[1] : null
    }
    if(!id) return false;

    const url = `https://player.vimeo.com/video/${id}`
    const v = DOMParser.parseFromString(`~~~ embed:${id} vimeo ~~~`)
    child.parentNode.replaceChild(v, child)
    if(links) links.add(url)

    // Preview image requires a callback.. http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-vimeo
    // if(images) images.add('https://.../vi/' + id + '/0.jpg')

    return true
} catch(error) {console.log(error); return false}}

function ipfsPrefix(url) {
    if($STM_Config.ipfs_prefix) {
        // Convert //ipfs/xxx  or /ipfs/xxx  into  https://steemit.com/ipfs/xxxxx
        if(/^\/?\/ipfs\//.test(url)) {
            const slash = url.charAt(1) === '/' ? 1 : 0
            url = url.substring(slash + '/ipfs/'.length) // start with only 1 /
            return $STM_Config.ipfs_prefix + '/' + url
        }
    }
    return url
}
<commit_msg>Closes #303/12<commit_after>import xmldom from 'xmldom'
import linksRe from 'app/utils/Links'
import {validate_account_name} from 'app/utils/ChainValidation'

const noop = () => {}
const DOMParser = new xmldom.DOMParser({
    errorHandler: {warning: noop, error: noop}
})
const XMLSerializer = new xmldom.XMLSerializer()

/**
 * Functions performed by HTMLReady
 *
 * State reporting
 *  - hashtags: collect all #tags in content
 *  - usertags: collect all @mentions in content
 *  - htmltags: collect all html <tags> used (for validation)
 *  - images: collect all image URLs in content
 *  - links: collect all href URLs in content
 *
 * Mutations
 *  - link()
 *    - ensure all  href's begin with a protocol. prepend https:// otherwise.
 *  - iframe()
 *    - wrap all s in
 for responsive sizing
 *  - img()
 *    - convert any  src IPFS prefixes to standard URL
 *    - change relative protocol to https://
 *  - linkifyNode()
 *    - scans text content to be turned into rich content
 *    - embedYouTubeNode()
 *      - identify plain youtube URLs and prep them for "rich embed"
 *    - linkify()
 *      - scan text for:
 *        - #tags, convert to  links
 *        - @mentions, convert to  links
 *        - naked URLs
 *          - if img URL, normalize URL and convert to  tag
 *          - otherwise, normalize URL and convert to  link
 *  - proxifyImages()
 *    - prepend proxy URL to any non-local  src's
 *
 * We could implement 2 levels of HTML mutation for maximum reuse:
 *  1. Normalization of HTML - non-proprietary, pre-rendering cleanup/normalization
 *    - (state reporting done at this level)
 *    - normalize URL protocols
 *    - convert naked URLs to images/links
 *    - convert embeddable URLs to s
 *    - basic sanitization?
 *  2. Steemit.com Rendering - add in proprietary Steemit.com functions/links
 *    - convert s to custom objects
 *    - linkify #tags and @mentions
 *    - proxify images
 *
 */

/** Split the HTML on top-level elements. This allows react to compare separately, preventing excessive re-rendering.
 * Used in MarkdownViewer.jsx
 */
// export function sectionHtml (html) {
//   const doc = DOMParser.parseFromString(html, 'text/html')
//   const sections = Array(...doc.childNodes).map(child => XMLSerializer.serializeToString(child))
//   return sections
// }

/** Embed videos, link mentions and hashtags, etc...
*/
export default function (html, {mutate = true} = {}) {
    const state = {mutate}
    state.hashtags = new Set()
    state.usertags = new Set()
    state.htmltags = new Set()
    state.images = new Set()
    state.links = new Set()
    try {
        const doc = DOMParser.parseFromString(html, 'text/html')
        traverse(doc, state)
        if(mutate) proxifyImages(doc)
        // console.log('state', state)
        if(!mutate) return state
        return {html: (doc) ? XMLSerializer.serializeToString(doc) : '', ...state}
    }catch(error) {
        // Not Used, parseFromString might throw an error in the future
        console.error(error.toString())
        return {html}
    }
}

function traverse(node, state, depth = 0) {
    if(!node || !node.childNodes) return
    Array(...node.childNodes).forEach(child => {
        // console.log(depth, 'child.tag,data', child.tagName, child.data)
        const tag = child.tagName ? child.tagName.toLowerCase() : null
        if(tag) state.htmltags.add(tag)

        if(tag === 'img')
            img(state, child)
        else if(tag === 'iframe')
            iframe(state, child)
        else if(tag === 'a')
            link(state, child)
        else if(child.nodeName === '#text')
            linkifyNode(child, state)

        traverse(child, state, depth + 1)
    })
}

function link(state, child) {
    const url = child.getAttribute('href')
    if(url) {
        state.links.add(url)
        if(state.mutate) {
            // If this link is not relative, http, or https -- add https.
            if(! /^\/(?!\/)|(https?:)?\/\//.test(url)) {
                child.setAttribute('href', "https://"+url)
            }
        }
    }
}

// wrap iframes in div.videoWrapper to control size/aspect ratio
function iframe(state, child) {
    const {mutate} = state
    if(!mutate) return

    const tag = child.parentNode.tagName ? child.parentNode.tagName.toLowerCase() : child.parentNode.tagName
    if(tag == 'div' && child.parentNode.getAttribute('class') == 'videoWrapper') return;
    const html = XMLSerializer.serializeToString(child)
    child.parentNode.replaceChild(DOMParser.parseFromString(`<div class="videoWrapper">${html}</div>`), child)
}

function img(state, child) {
    const url = child.getAttribute('src')
    if(url) {
        state.images.add(url)
        if(state.mutate) {
            let url2 = ipfsPrefix(url)
            if(/^\/\//.test(url2)) {
                // Change relative protocol imgs to https
                url2 = "https:" + url2
            }
            if(url2 !== url) {
                child.setAttribute('src', url2)
            }
        }
    }
}

// For all img elements with non-local URLs, prepend the proxy URL (e.g. `https://img0.steemit.com/0x0/`)
function proxifyImages(doc) {
    if (!$STM_Config.img_proxy_prefix) return
    if (!doc) return;
    [...doc.getElementsByTagName('img')].forEach(node => {
        const url = node.getAttribute('src')
        if(! linksRe.local.test(url))
            node.setAttribute('src', $STM_Config.img_proxy_prefix + '0x0/' + url)
    })
}

function linkifyNode(child, state) {try{
    const tag = child.parentNode.tagName ? child.parentNode.tagName.toLowerCase() : child.parentNode.tagName
    if(tag === 'code') return
    if(tag === 'a') return

    const {mutate} = state
    if(!child.data) return
    if(embedYouTubeNode(child, state.links, state.images)) return
    if(embedVimeoNode(child, state.links, state.images)) return

    const data = XMLSerializer.serializeToString(child)
    const content = linkify(data, state.mutate, state.hashtags, state.usertags, state.images, state.links)
    if(mutate && content !== data) {
        child.parentNode.replaceChild(DOMParser.parseFromString(`<span>${content}</span>`), child)
    }
} catch(error) {console.log(error)}}

function linkify(content, mutate, hashtags, usertags, images, links) {
    // hashtag
    content = content.replace(/(^|\s)(#[-a-z\d]+)/ig, tag => {
        if(/#[\d]+$/.test(tag)) return tag // Don't allow numbers to be tags
        const space = /^\s/.test(tag) ? tag[0] : ''
        const tag2 = tag.trim().substring(1)
        const tagLower = tag2.toLowerCase()
        if(hashtags) hashtags.add(tagLower)
        if(!mutate) return tag
        return space + `<a href="/trending/${tagLower}">${tag}</a>`
    })

    // usertag (mention)
    content = content.replace(/(^|[^\w\/]|\s)@([a-z][-\.a-z\d]+[a-z\d])(^|[^\w\/]|\s)/ig, (match, symbol, user) => {
        // console.log('['+match+']', '['+symbol+']', '['+user+']')
        const userLower = user.toLowerCase()
        const valid = validate_account_name(userLower) == null
        if(valid && usertags) usertags.add(userLower)
        if(!mutate) return match
        return valid ? `${symbol}<a href="/@${userLower}">@${user}</a>` : symbol + '@' + user
    })

    content = content.replace(linksRe.any, ln => {
        if(linksRe.image.test(ln)) {
            if(images) images.add(ln)
            return `<img src="${ipfsPrefix(ln)}" />`
        }
        if(links) links.add(ln)
        return `<a href="${ipfsPrefix(ln)}">${ln}</a>`
    })
    return content
}

function embedYouTubeNode(child, links, images) {try{
    if(!child.data) return false
    const data = child.data

    let url
    {
        const m = data.match(linksRe.youTube)
        url = m ? m[0] : null
    }
    if(!url) return false;

    let id
    {
        const m = url.match(linksRe.youTubeId)
        id = m && m.length >=2 ? m[1] : null
    }
    if(!id) return false

    const v = DOMParser.parseFromString(`~~~ embed:${id} youtube ~~~`)
    child.parentNode.replaceChild(v, child)
    if(links) links.add(url)
    if(images) images.add('https://img.youtube.com/vi/' + id + '/0.jpg')
    return true
} catch(error) {console.log(error); return false}}

function embedVimeoNode(child, links, /*images*/) {try{
    if(!child.data) return false
    const data = child.data

    let id
    {
        const m = data.match(linksRe.vimeoId)
        id = m && m.length >= 2 ? m[1] : null
    }
    if(!id) return false;

    const url = `https://player.vimeo.com/video/${id}`
    const v = DOMParser.parseFromString(`~~~ embed:${id} vimeo ~~~`)
    child.parentNode.replaceChild(v, child)
    if(links) links.add(url)

    // Preview image requires a callback.. http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-vimeo
    // if(images) images.add('https://.../vi/' + id + '/0.jpg')

    return true
} catch(error) {console.log(error); return false}}

function ipfsPrefix(url) {
    if($STM_Config.ipfs_prefix) {
        // Convert //ipfs/xxx  or /ipfs/xxx  into  https://steemit.com/ipfs/xxxxx
        if(/^\/?\/ipfs\//.test(url)) {
            const slash = url.charAt(1) === '/' ? 1 : 0
            url = url.substring(slash + '/ipfs/'.length) // start with only 1 /
            return $STM_Config.ipfs_prefix + '/' + url
        }
    }
    return url
}
<|endoftext|>


<commit_before>/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var Lib = require('../../lib');
var Color = require('../../components/color');

var boxDefaults = require('../box/defaults');
var attributes = require('./attributes');

module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
    function coerce(attr, dflt) {
        return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
    }
    function coerce2(attr, dflt) {
        return Lib.coerce2(traceIn, traceOut, attributes, attr, dflt);
    }

    boxDefaults.handleSampleDefaults(traceIn, traceOut, coerce, layout);
    if(traceOut.visible === false) return;

    coerce('bandwidth');
    coerce('side');

    var width = coerce('width');
    if(!width) {
        coerce('scalegroup', traceOut.name);
        coerce('scalemode');
    } else {
        // set scalegroup and scalemode to dflts if width
        traceOut.scalegroup =  '';
        traceOut.scalemode = 'width';
    }

    var span = coerce('span');
    var spanmodeDflt;
    if(Array.isArray(span)) spanmodeDflt = 'manual';
    coerce('spanmode', spanmodeDflt);

    var lineColor = coerce('line.color', (traceIn.marker || {}).color || defaultColor);
    var lineWidth = coerce('line.width');
    var fillColor = coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5));

    boxDefaults.handlePointsDefaults(traceIn, traceOut, coerce, {prefix: ''});

    var boxWidth = coerce2('box.width');
    var boxFillColor = coerce2('box.fillcolor', fillColor);
    var boxLineColor = coerce2('box.line.color', lineColor);
    var boxLineWidth = coerce2('box.line.width', lineWidth);
    var boxVisible = coerce('box.visible', Boolean(boxWidth || boxFillColor || boxLineColor || boxLineWidth));
    if(!boxVisible) traceOut.box = {visible: false};

    var meanLineColor = coerce2('meanline.color', lineColor);
    var meanLineWidth = coerce2('meanline.width', lineWidth);
    var meanLineVisible = coerce('meanline.visible', Boolean(meanLineColor || meanLineWidth));
    if(!meanLineVisible) traceOut.meanline = {visible: false};
};
<commit_msg>fix up the syntax of violin/defaults<commit_after>/**
* Copyright 2012-2018, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var Lib = require('../../lib');
var Color = require('../../components/color');

var boxDefaults = require('../box/defaults');
var attributes = require('./attributes');

module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
    function coerce(attr, dflt) {
        return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
    }
    function coerce2(attr, dflt) {
        return Lib.coerce2(traceIn, traceOut, attributes, attr, dflt);
    }

    boxDefaults.handleSampleDefaults(traceIn, traceOut, coerce, layout);
    if(traceOut.visible === false) return;

    coerce('bandwidth');
    coerce('side');
    var width = coerce('width');
    if(!width) {
        coerce('scalegroup', traceOut.name);
        coerce('scalemode');
    } else {
        traceOut.scalegroup =  '';
        traceOut.scalemode = 'width';
    }

    var span = coerce('span');
    var spanmodeDflt;
    if(Array.isArray(span)) spanmodeDflt = 'manual';
    coerce('spanmode', spanmodeDflt);

    var lineColor = coerce('line.color', (traceIn.marker || {}).color || defaultColor);
    var lineWidth = coerce('line.width');
    var fillColor = coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5));

    boxDefaults.handlePointsDefaults(traceIn, traceOut, coerce, {prefix: ''});

    var boxWidth = coerce2('box.width');
    var boxFillColor = coerce2('box.fillcolor', fillColor);
    var boxLineColor = coerce2('box.line.color', lineColor);
    var boxLineWidth = coerce2('box.line.width', lineWidth);
    var boxVisible = coerce('box.visible', Boolean(boxWidth || boxFillColor || boxLineColor || boxLineWidth));
    if(!boxVisible) traceOut.box = {visible: false};

    var meanLineColor = coerce2('meanline.color', lineColor);
    var meanLineWidth = coerce2('meanline.width', lineWidth);
    var meanLineVisible = coerce('meanline.visible', Boolean(meanLineColor || meanLineWidth));
    if(!meanLineVisible) traceOut.meanline = {visible: false};
};
<|endoftext|>


<commit_before>/* global window, document */

;(function(win, doc) {
    'use strict';

    // TODO: Support `set` functionality to send data
    // TODO: Support `call` functionality to denote calling
    // TODO: Support error handling

    var _listeners = {};
    var _providers = {};

    // TODO: Error handling

    var _frameByName = function(frameName) {
        // Avoid memory garbage

        if (frameName === '_parent') {
            return window.parent;
        } else {
            // TODO: Filter frames by name as well
            var frame = document.getElementsByName(frameName)[0];

            if (!frame) {
                return null;
            }

            if (frame.contentWindow) {
                return frame.contentWindow;
            } else {
                return null;
            }
        }
    };

    var _generateNounce = function() {
        var size = 12;
        var text = "";
        var possible = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
                        "abcdefghijklmnopqrstuvwxyz" +
                        "0123456789");

        for (var i = 0; i < size; i++) {
            text += possible.charAt(Math.floor(Math.random() * possible.length));
        }

        return '_okra_' + text;
    };

    var _notifyListeners = function (event) {
        if (!_listeners.hasOwnProperty(event.origin)) {
            console.error('Cannot verify that the origin message is actually expected');
            return;
        }

        var nounce = event.data.nounce;

        if (!nounce || nounce.indexOf('_okra_') !== 0) {
            console.error('Cannot verify that it is a valid listener');
            return;
        }

        if (!_listeners[event.origin].hasOwnProperty(nounce)) {
            console.error('Cannot verify that the nonce exists');
            return;
        }

        var listener = _listeners[event.origin][nounce];

        if (event.source !== _frameByName(listener.frameName)) {
            console.error('Cannot verify that the exact frame is the one sending the event');
            return;
        }

        listener(event.data);
    };

    var _handleRequest = function (event) {
        if (!event.data || !event.data.name) {
            console.error('Request don`t have valid handler name');
            return;
        }

        // Wrap this check in a function
        if (!_providers.hasOwnProperty(event.data.name) ||
            !_providers[event.data.name]) {
            // TODO: Add the parameters to the error log
            console.error('No such provider was found');
            return;
        }

        var providerVariants = _providers[event.data.name];

        if (!providerVariants.hasOwnProperty(event.data.action) ||
            !providerVariants[event.data.action]) {
            console.error('No such provider variant was found');
            return;
        }

        var provider = providerVariants[event.data.action];

        if (!provider.isAllowed(event.origin)) {
            console.error('Origin is not allowed for this provider');
            return;
        }

        if ('set' === event.data.action) {
            provider.callback(event.data.value);
        } else if ('get' === event.data.action) {
            if ("string" !== typeof event.data.nounce &&
                event.data.nounce.indexOf('_okra_') !== 0) {
                console.error('Invalid nonce has been provided');
                return;
            }

            var value = provider.callback();
            event.source.postMessage({
                type: 'response',
                action: 'get',
                value: value,
                nounce: event.data.nounce
            }, event.origin);
        } else {
            console.error('Action was not recognized', event.data.action);
        }
    };

    window.addEventListener('message', function (event) {
        if (event.data) {
            if (event.data.type === 'request') {
                _handleRequest(event);
            } else if (event.data.type === 'response') {
                _notifyListeners(event);
            }
        }
    }, false);

    var _referrerToOrigin = function () {
        if (!document.referrer) {
            return null;
        }

        var a = document.createElement('a');
        a.href = document.referrer;
        return a.origin;
    };

    // Send `childLoaded` event to the parent
    if (window !== window.parent) {
        // TODO: Check for possible security issues in allowing
        //       referrer blindly
        // TODO: Convert to a faster event, such as DOMReady
        window.addEventListener('load', function () {
            window.parent.postMessage({
               type: "childLoaded"
            }, _referrerToOrigin());
        }, false);
    }

    var createInlet = function(frameName, origin) {
        var _messagesQueue = [];

        // TODO: Really? that's a lousy check
        var _isFrameLoaded = window !== window.parent;

        // TODO: Find a better name for this
        var _realPostMessage = function (message) {
            // TODO: Rename `_frameByName` to `_windowByFrameName`
            var frame = _frameByName(frameName);
            frame.postMessage(message, origin);
        };

        var _postMessage = function (message) {
            if (_isFrameLoaded) {
                _realPostMessage(message);
            } else {
                _messagesQueue.push(message);
            }
        };

        // Detect a childLoad event
        var _loadListener = function (event) {
            var frameWin = _frameByName(frameName);
            if (event.origin === origin && event.source === frameWin) {
                if (event.data && 'childLoaded' === event.data.type) {
                    while (_messagesQueue.length) {
                        var message = _messagesQueue.pop();
                        _realPostMessage(message);
                    }

                    _isFrameLoaded = true;

                    window.removeEventListener(
                        'message',
                        _loadListener,
                        false
                    );
                }
            }
        };

        window.addEventListener('message', _loadListener, false);

        var _getValue = function (valueName, cb) {
            // TODO: Check for action??
            var nounce = _generateNounce();

            _listeners[origin] = _listeners[origin] || {};

            _listeners[origin][nounce] = function(data) {
                cb(data.value);
                delete _listeners[origin][nounce];
            };

            _listeners[origin][nounce].nounce = nounce;
            _listeners[origin][nounce].frameName = frameName;

            _postMessage({
                type: 'request',
                action: 'get',
                name: valueName,
                nounce: nounce
            });
        };

        var _setValue = function (valueName, value) {
            _postMessage({
                type: 'request',
                action: 'set',
                name: valueName,
                value: value
            });
        };

        return {
            get: _getValue,
            set: _setValue
        };
    };

    var createProvider = function (action, name, cb) {
        var _allowedOrigins = {};

        var provider = {
            callback: cb,
            // TODO: Support origin globe
            // TODO: Support `deny` and `destroy` methods
            allow: function (origin) {
                if (origin === document.referrer) {
                    origin = _referrerToOrigin();
                }

                _allowedOrigins[origin] = true;
                return provider; // Allow subsequent `allow()` calls
            },
            isAllowed: function (origin) {
                if (origin && _allowedOrigins.hasOwnProperty(origin)) {
                    return !!_allowedOrigins[origin];
                } else {
                    return false;
                }
            }
        };

        _providers[name] = _providers[name] || {};
        _providers[name][action] = provider;

        return provider;
    };

    var Okra = {
        inlet: createInlet,
        provide: createProvider
    };

    win.Okra = Okra;
}(window, document));
<commit_msg>Refactored `allow()` to create another method `allowReferrer()`<commit_after>/* global window, document */

;(function(win, doc) {
    'use strict';

    // TODO: Support `set` functionality to send data
    // TODO: Support `call` functionality to denote calling
    // TODO: Support error handling

    var _listeners = {};
    var _providers = {};

    // TODO: Error handling

    var _frameByName = function(frameName) {
        // Avoid memory garbage

        if (frameName === '_parent') {
            return window.parent;
        } else {
            // TODO: Filter frames by name as well
            var frame = document.getElementsByName(frameName)[0];

            if (!frame) {
                return null;
            }

            if (frame.contentWindow) {
                return frame.contentWindow;
            } else {
                return null;
            }
        }
    };

    var _generateNounce = function() {
        var size = 12;
        var text = "";
        var possible = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
                        "abcdefghijklmnopqrstuvwxyz" +
                        "0123456789");

        for (var i = 0; i < size; i++) {
            text += possible.charAt(Math.floor(Math.random() * possible.length));
        }

        return '_okra_' + text;
    };

    var _notifyListeners = function (event) {
        if (!_listeners.hasOwnProperty(event.origin)) {
            console.error('Cannot verify that the origin message is actually expected');
            return;
        }

        var nounce = event.data.nounce;

        if (!nounce || nounce.indexOf('_okra_') !== 0) {
            console.error('Cannot verify that it is a valid listener');
            return;
        }

        if (!_listeners[event.origin].hasOwnProperty(nounce)) {
            console.error('Cannot verify that the nonce exists');
            return;
        }

        var listener = _listeners[event.origin][nounce];

        if (event.source !== _frameByName(listener.frameName)) {
            console.error('Cannot verify that the exact frame is the one sending the event');
            return;
        }

        listener(event.data);
    };

    var _handleRequest = function (event) {
        if (!event.data || !event.data.name) {
            console.error('Request don`t have valid handler name');
            return;
        }

        // Wrap this check in a function
        if (!_providers.hasOwnProperty(event.data.name) ||
            !_providers[event.data.name]) {
            // TODO: Add the parameters to the error log
            console.error('No such provider was found');
            return;
        }

        var providerVariants = _providers[event.data.name];

        if (!providerVariants.hasOwnProperty(event.data.action) ||
            !providerVariants[event.data.action]) {
            console.error('No such provider variant was found');
            return;
        }

        var provider = providerVariants[event.data.action];

        if (!provider.isAllowed(event.origin)) {
            console.error('Origin is not allowed for this provider');
            return;
        }

        if ('set' === event.data.action) {
            provider.callback(event.data.value);
        } else if ('get' === event.data.action) {
            if ("string" !== typeof event.data.nounce &&
                event.data.nounce.indexOf('_okra_') !== 0) {
                console.error('Invalid nonce has been provided');
                return;
            }

            var value = provider.callback();
            event.source.postMessage({
                type: 'response',
                action: 'get',
                value: value,
                nounce: event.data.nounce
            }, event.origin);
        } else {
            console.error('Action was not recognized', event.data.action);
        }
    };

    window.addEventListener('message', function (event) {
        if (event.data) {
            if (event.data.type === 'request') {
                _handleRequest(event);
            } else if (event.data.type === 'response') {
                _notifyListeners(event);
            }
        }
    }, false);

    var _referrerToOrigin = function () {
        if (!document.referrer) {
            return null;
        }

        var a = document.createElement('a');
        a.href = document.referrer;
        return a.origin;
    };

    // Send `childLoaded` event to the parent
    if (window !== window.parent) {
        // TODO: Check for possible security issues in allowing
        //       referrer blindly
        // TODO: Convert to a faster event, such as DOMReady
        window.addEventListener('load', function () {
            window.parent.postMessage({
               type: "childLoaded"
            }, _referrerToOrigin());
        }, false);
    }

    var createInlet = function(frameName, origin) {
        var _messagesQueue = [];

        // TODO: Really? that's a lousy check
        var _isFrameLoaded = window !== window.parent;

        // TODO: Find a better name for this
        var _realPostMessage = function (message) {
            // TODO: Rename `_frameByName` to `_windowByFrameName`
            var frame = _frameByName(frameName);
            frame.postMessage(message, origin);
        };

        var _postMessage = function (message) {
            if (_isFrameLoaded) {
                _realPostMessage(message);
            } else {
                _messagesQueue.push(message);
            }
        };

        // Detect a childLoad event
        var _loadListener = function (event) {
            var frameWin = _frameByName(frameName);
            if (event.origin === origin && event.source === frameWin) {
                if (event.data && 'childLoaded' === event.data.type) {
                    while (_messagesQueue.length) {
                        var message = _messagesQueue.pop();
                        _realPostMessage(message);
                    }

                    _isFrameLoaded = true;

                    window.removeEventListener(
                        'message',
                        _loadListener,
                        false
                    );
                }
            }
        };

        window.addEventListener('message', _loadListener, false);

        var _getValue = function (valueName, cb) {
            // TODO: Check for action??
            var nounce = _generateNounce();

            _listeners[origin] = _listeners[origin] || {};

            _listeners[origin][nounce] = function(data) {
                cb(data.value);
                delete _listeners[origin][nounce];
            };

            _listeners[origin][nounce].nounce = nounce;
            _listeners[origin][nounce].frameName = frameName;

            _postMessage({
                type: 'request',
                action: 'get',
                name: valueName,
                nounce: nounce
            });
        };

        var _setValue = function (valueName, value) {
            _postMessage({
                type: 'request',
                action: 'set',
                name: valueName,
                value: value
            });
        };

        return {
            get: _getValue,
            set: _setValue
        };
    };

    var createProvider = function (action, name, cb) {
        var _allowedOrigins = {};

        var provider = {
            callback: cb,
            // TODO: Support origin globe
            // TODO: Support `deny` and `destroy` methods
            allow: function (origin) {
                _allowedOrigins[origin] = true;
                return provider; // Allow subsequent `allow()` calls
            },
            // TODO: This is vague and may allow security issues, such as
            //       allowing the redirector of an iframe to access a page?
            //       Seems dangerous, perhaps it should be limited to the
            //       Referrer when it is the `window.parent`. It could be even
            //       renamed to `allowReferrerTop()`
            allowReferrer: function () {
                return provider.allow(_referrerToOrigin());
            },
            isAllowed: function (origin) {
                if (origin && _allowedOrigins.hasOwnProperty(origin)) {
                    return !!_allowedOrigins[origin];
                } else {
                    return false;
                }
            }
        };

        _providers[name] = _providers[name] || {};
        _providers[name][action] = provider;

        return provider;
    };

    var Okra = {
        inlet: createInlet,
        provide: createProvider
    };

    win.Okra = Okra;
}(window, document));
<|endoftext|>


<commit_before>'use strict';

const delay = require('delay');
const MongoClient = require('mongodb').MongoClient;

const config = require('./config');

function PubChemConnection() {}

PubChemConnection.prototype.close = function close() {
  if (this.connection) return this.connection.close();
  return undefined;
};

PubChemConnection.prototype.getMoleculesCollection = async function getDatabase() {
  return (await this.getDatabase()).collection('molecules');
};

PubChemConnection.prototype.getAdminCollection = async function getDatabase() {
  return (await this.getDatabase()).collection('admin');
};

PubChemConnection.prototype.getMfsCollection = async function getDatabase() {
  return (await this.getDatabase()).collection('mfs');
};

PubChemConnection.prototype.getMfStatsCollection = async function getDatabase() {
  return (await this.getDatabase()).collection('mfstats');
};

PubChemConnection.prototype.getDatabase = async function getDatabase() {
  while (true) {
    try {
      await this.init();
      break;
    } catch (e) {
      console.log('Connection to mongo failed, waiting 5s');
      await delay(5000);
    }
  }
  return this.connection.db(config.databaseName);
};

PubChemConnection.prototype.getCollection = async function getCollection(collectionName) {
  return (await this.getDatabase()).collection(collectionName);
};

PubChemConnection.prototype.init = async function init() {
  if (this.connection) return;
  this.connection = await MongoClient.connect(config.mongodbUrl, {
    autoReconnect: true,
    socketTimeoutMS: 60 * 60 * 1000
  });
};

module.exports = PubChemConnection;
<commit_msg>add sockettimeoutMS<commit_after>'use strict';

const delay = require('delay');
const MongoClient = require('mongodb').MongoClient;

const config = require('./config');

function PubChemConnection() {}

PubChemConnection.prototype.close = function close() {
  if (this.connection) return this.connection.close();
  return undefined;
};

PubChemConnection.prototype.getMoleculesCollection = async function getDatabase() {
  return (await this.getDatabase()).collection('molecules');
};

PubChemConnection.prototype.getAdminCollection = async function getDatabase() {
  return (await this.getDatabase()).collection('admin');
};

PubChemConnection.prototype.getMfsCollection = async function getDatabase() {
  return (await this.getDatabase()).collection('mfs');
};

PubChemConnection.prototype.getMfStatsCollection = async function getDatabase() {
  return (await this.getDatabase()).collection('mfstats');
};

PubChemConnection.prototype.getDatabase = async function getDatabase() {
  while (true) {
    try {
      await this.init();
      break;
    } catch (e) {
      console.log('Connection to mongo failed, waiting 5s');
      await delay(5000);
    }
  }
  return this.connection.db(config.databaseName);
};

PubChemConnection.prototype.getCollection = async function getCollection(collectionName) {
  return (await this.getDatabase()).collection(collectionName);
};

PubChemConnection.prototype.init = async function init() {
  if (this.connection) return;
  this.connection = await MongoClient.connect(config.mongodbUrl, {
    autoReconnect: true,
    connectTimeoutMS: 60 * 60 * 1000,
    socketTimeoutMS: 60 * 60 * 1000,
  });
};

module.exports = PubChemConnection;
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict
 */

import invariant from '../jsutils/invariant';
import keyMap from '../jsutils/keyMap';
import objectValues from '../jsutils/objectValues';
import { ASTDefinitionBuilder } from './buildASTSchema';
import { GraphQLError } from '../error/GraphQLError';
import { isSchema, GraphQLSchema } from '../type/schema';
import { isIntrospectionType } from '../type/introspection';

import type { GraphQLSchemaValidationOptions } from '../type/schema';

import {
  isObjectType,
  isInterfaceType,
  isUnionType,
  isListType,
  isNonNullType,
  GraphQLList,
  GraphQLNonNull,
  GraphQLObjectType,
  GraphQLInterfaceType,
  GraphQLUnionType,
} from '../type/definition';

import { GraphQLDirective } from '../type/directives';

import { Kind } from '../language/kinds';

import type { GraphQLType, GraphQLNamedType } from '../type/definition';
import type {
  DocumentNode,
  DirectiveDefinitionNode,
  SchemaExtensionNode,
} from '../language/ast';

type Options = {|
  ...GraphQLSchemaValidationOptions,

  /**
   * Descriptions are defined as preceding string literals, however an older
   * experimental version of the SDL supported preceding comments as
   * descriptions. Set to true to enable this deprecated behavior.
   *
   * Default: false
   */
  commentDescriptions?: boolean,
|};

/**
 * Produces a new schema given an existing schema and a document which may
 * contain GraphQL type extensions and definitions. The original schema will
 * remain unaltered.
 *
 * Because a schema represents a graph of references, a schema cannot be
 * extended without effectively making an entire copy. We do not know until it's
 * too late if subgraphs remain unchanged.
 *
 * This algorithm copies the provided schema, applying extensions while
 * producing the copy. The original schema remains unaltered.
 *
 * Accepts options as a third argument:
 *
 *    - commentDescriptions:
 *        Provide true to use preceding comments as the description.
 *
 */
export function extendSchema(
  schema: GraphQLSchema,
  documentAST: DocumentNode,
  options?: Options,
): GraphQLSchema {
  invariant(isSchema(schema), 'Must provide valid GraphQLSchema');

  invariant(
    documentAST && documentAST.kind === Kind.DOCUMENT,
    'Must provide valid Document AST',
  );

  // Collect the type definitions and extensions found in the document.
  const typeDefinitionMap = Object.create(null);
  const typeExtensionsMap = Object.create(null);

  // New directives and types are separate because a directives and types can
  // have the same name. For example, a type named "skip".
  const directiveDefinitions: Array<DirectiveDefinitionNode> = [];

  // Schema extensions are collected which may add additional operation types.
  const schemaExtensions: Array<SchemaExtensionNode> = [];

  for (let i = 0; i < documentAST.definitions.length; i++) {
    const def = documentAST.definitions[i];
    switch (def.kind) {
      case Kind.SCHEMA_DEFINITION:
        // Sanity check that a schema extension is not defining a new schema
        throw new GraphQLError(
          'Cannot define a new schema within a schema extension.',
          [def],
        );
      case Kind.SCHEMA_EXTENSION:
        schemaExtensions.push(def);
        break;
      case Kind.OBJECT_TYPE_DEFINITION:
      case Kind.INTERFACE_TYPE_DEFINITION:
      case Kind.ENUM_TYPE_DEFINITION:
      case Kind.UNION_TYPE_DEFINITION:
      case Kind.SCALAR_TYPE_DEFINITION:
      case Kind.INPUT_OBJECT_TYPE_DEFINITION:
        // Sanity check that none of the defined types conflict with the
        // schema's existing types.
        const typeName = def.name.value;
        if (schema.getType(typeName)) {
          throw new GraphQLError(
            `Type "${typeName}" already exists in the schema. It cannot also ` +
              'be defined in this type definition.',
            [def],
          );
        }
        typeDefinitionMap[typeName] = def;
        break;
      case Kind.OBJECT_TYPE_EXTENSION:
      case Kind.INTERFACE_TYPE_EXTENSION:
        // Sanity check that this type extension exists within the
        // schema's existing types.
        const extendedTypeName = def.name.value;
        const existingType = schema.getType(extendedTypeName);
        if (!existingType) {
          throw new GraphQLError(
            `Cannot extend type "${extendedTypeName}" because it does not ` +
              'exist in the existing schema.',
            [def],
          );
        }
        checkExtensionNode(existingType, def);

        const existingTypeExtensions = typeExtensionsMap[extendedTypeName];
        typeExtensionsMap[extendedTypeName] = existingTypeExtensions
          ? existingTypeExtensions.concat([def])
          : [def];
        break;
      case Kind.DIRECTIVE_DEFINITION:
        const directiveName = def.name.value;
        const existingDirective = schema.getDirective(directiveName);
        if (existingDirective) {
          throw new GraphQLError(
            `Directive "${directiveName}" already exists in the schema. It ` +
              'cannot be redefined.',
            [def],
          );
        }
        directiveDefinitions.push(def);
        break;
      case Kind.SCALAR_TYPE_EXTENSION:
      case Kind.UNION_TYPE_EXTENSION:
      case Kind.ENUM_TYPE_EXTENSION:
      case Kind.INPUT_OBJECT_TYPE_EXTENSION:
        throw new Error(
          `The ${def.kind} kind is not yet supported by extendSchema().`,
        );
    }
  }

  // If this document contains no new types, extensions, or directives then
  // return the same unmodified GraphQLSchema instance.
  if (
    Object.keys(typeExtensionsMap).length === 0 &&
    Object.keys(typeDefinitionMap).length === 0 &&
    directiveDefinitions.length === 0 &&
    schemaExtensions.length === 0
  ) {
    return schema;
  }

  const astBuilder = new ASTDefinitionBuilder(
    typeDefinitionMap,
    options,
    typeRef => {
      const typeName = typeRef.name.value;
      const existingType = schema.getType(typeName);
      if (existingType) {
        return extendNamedType(existingType);
      }

      throw new GraphQLError(
        `Unknown type: "${typeName}". Ensure that this type exists ` +
          'either in the original schema, or is added in a type definition.',
        [typeRef],
      );
    },
  );

  const extendTypeCache = Object.create(null);

  // Get the extended root operation types.
  const operationTypes = {
    query: extendMaybeNamedType(schema.getQueryType()),
    mutation: extendMaybeNamedType(schema.getMutationType()),
    subscription: extendMaybeNamedType(schema.getSubscriptionType()),
  };

  // Then, incorporate all schema extensions.
  schemaExtensions.forEach(schemaExtension => {
    if (schemaExtension.operationTypes) {
      schemaExtension.operationTypes.forEach(operationType => {
        const operation = operationType.operation;
        if (operationTypes[operation]) {
          throw new Error(`Must provide only one ${operation} type in schema.`);
        }
        const typeRef = operationType.type;
        // Note: While this could make early assertions to get the correctly
        // typed values, that would throw immediately while type system
        // validation with validateSchema() will produce more actionable results.
        operationTypes[operation] = (astBuilder.buildType(typeRef): any);
      });
    }
  });

  const schemaExtensionASTNodes = schemaExtensions
    ? schema.extensionASTNodes
      ? schema.extensionASTNodes.concat(schemaExtensions)
      : schemaExtensions
    : schema.extensionASTNodes;

  const types = [
    // Iterate through all types, getting the type definition for each, ensuring
    // that any type not directly referenced by a field will get created.
    ...objectValues(schema.getTypeMap()).map(type => extendNamedType(type)),
    // Do the same with new types.
    ...astBuilder.buildTypes(objectValues(typeDefinitionMap)),
  ];

  // Support both original legacy names and extended legacy names.
  const schemaAllowedLegacyNames = schema.__allowedLegacyNames;
  const extendAllowedLegacyNames = options && options.allowedLegacyNames;
  const allowedLegacyNames =
    schemaAllowedLegacyNames && extendAllowedLegacyNames
      ? schemaAllowedLegacyNames.concat(extendAllowedLegacyNames)
      : schemaAllowedLegacyNames || extendAllowedLegacyNames;

  // Then produce and return a Schema with these types.
  return new GraphQLSchema({
    query: operationTypes.query,
    mutation: operationTypes.mutation,
    subscription: operationTypes.subscription,
    types,
    directives: getMergedDirectives(),
    astNode: schema.astNode,
    extensionASTNodes: schemaExtensionASTNodes,
    allowedLegacyNames,
  });

  // Below are functions used for producing this schema that have closed over
  // this scope and have access to the schema, cache, and newly defined types.

  function getMergedDirectives(): Array<GraphQLDirective> {
    const existingDirectives = schema.getDirectives();
    invariant(existingDirectives, 'schema must have default directives');

    return existingDirectives.concat(
      directiveDefinitions.map(node => astBuilder.buildDirective(node)),
    );
  }

  function extendMaybeNamedType<T: GraphQLNamedType>(type: ?T): ?T {
    return type ? extendNamedType(type) : null;
  }

  function extendNamedType<T: GraphQLNamedType>(type: T): T {
    if (isIntrospectionType(type)) {
      // Introspection types are not extended.
      return type;
    }

    if (!extendTypeCache[type.name]) {
      if (isObjectType(type)) {
        extendTypeCache[type.name] = extendObjectType(type);
      } else if (isInterfaceType(type)) {
        extendTypeCache[type.name] = extendInterfaceType(type);
      } else if (isUnionType(type)) {
        extendTypeCache[type.name] = extendUnionType(type);
      } else {
        // This type is not yet extendable.
        extendTypeCache[type.name] = type;
      }
    }
    return (extendTypeCache[type.name]: any);
  }

  function extendObjectType(type: GraphQLObjectType): GraphQLObjectType {
    const name = type.name;
    const extensionASTNodes = typeExtensionsMap[name]
      ? type.extensionASTNodes
        ? type.extensionASTNodes.concat(typeExtensionsMap[name])
        : typeExtensionsMap[name]
      : type.extensionASTNodes;
    return new GraphQLObjectType({
      name,
      description: type.description,
      interfaces: () => extendImplementedInterfaces(type),
      fields: () => extendFieldMap(type),
      astNode: type.astNode,
      extensionASTNodes,
      isTypeOf: type.isTypeOf,
    });
  }

  function extendInterfaceType(
    type: GraphQLInterfaceType,
  ): GraphQLInterfaceType {
    const name = type.name;
    const extensionASTNodes = typeExtensionsMap[name]
      ? type.extensionASTNodes
        ? type.extensionASTNodes.concat(typeExtensionsMap[name])
        : typeExtensionsMap[name]
      : type.extensionASTNodes;
    return new GraphQLInterfaceType({
      name: type.name,
      description: type.description,
      fields: () => extendFieldMap(type),
      astNode: type.astNode,
      extensionASTNodes,
      resolveType: type.resolveType,
    });
  }

  function extendUnionType(type: GraphQLUnionType): GraphQLUnionType {
    return new GraphQLUnionType({
      name: type.name,
      description: type.description,
      types: type.getTypes().map(extendNamedType),
      astNode: type.astNode,
      resolveType: type.resolveType,
    });
  }

  function extendImplementedInterfaces(
    type: GraphQLObjectType,
  ): Array<GraphQLInterfaceType> {
    const interfaces = type.getInterfaces().map(extendNamedType);

    // If there are any extensions to the interfaces, apply those here.
    const extensions = typeExtensionsMap[type.name];
    if (extensions) {
      extensions.forEach(extension => {
        extension.interfaces.forEach(namedType => {
          // Note: While this could make early assertions to get the correctly
          // typed values, that would throw immediately while type system
          // validation with validateSchema() will produce more actionable results.
          interfaces.push((astBuilder.buildType(namedType): any));
        });
      });
    }

    return interfaces;
  }

  function extendFieldMap(type: GraphQLObjectType | GraphQLInterfaceType) {
    const newFieldMap = Object.create(null);
    const oldFieldMap = type.getFields();
    Object.keys(oldFieldMap).forEach(fieldName => {
      const field = oldFieldMap[fieldName];
      newFieldMap[fieldName] = {
        description: field.description,
        deprecationReason: field.deprecationReason,
        type: extendType(field.type),
        args: keyMap(field.args, arg => arg.name),
        astNode: field.astNode,
        resolve: field.resolve,
      };
    });

    // If there are any extensions to the fields, apply those here.
    const extensions = typeExtensionsMap[type.name];
    if (extensions) {
      extensions.forEach(extension => {
        extension.fields.forEach(field => {
          const fieldName = field.name.value;
          if (oldFieldMap[fieldName]) {
            throw new GraphQLError(
              `Field "${type.name}.${fieldName}" already exists in the ` +
                'schema. It cannot also be defined in this type extension.',
              [field],
            );
          }
          newFieldMap[fieldName] = astBuilder.buildField(field);
        });
      });
    }

    return newFieldMap;
  }

  function extendType<T: GraphQLType>(typeDef: T): T {
    if (isListType(typeDef)) {
      return (GraphQLList(extendType(typeDef.ofType)): any);
    }
    if (isNonNullType(typeDef)) {
      return (GraphQLNonNull(extendType(typeDef.ofType)): any);
    }
    return extendNamedType(typeDef);
  }
}

function checkExtensionNode(type, node) {
  switch (node.kind) {
    case Kind.OBJECT_TYPE_EXTENSION:
      if (!isObjectType(type)) {
        throw new GraphQLError(
          `Cannot extend non-object type "${type.name}".`,
          [node],
        );
      }
      break;
    case Kind.INTERFACE_TYPE_EXTENSION:
      if (!isInterfaceType(type)) {
        throw new GraphQLError(
          `Cannot extend non-interface type "${type.name}".`,
          [node],
        );
      }
      break;
  }
}
<commit_msg>Minor simplification in extendSchema (#1349)<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict
 */

import invariant from '../jsutils/invariant';
import keyMap from '../jsutils/keyMap';
import objectValues from '../jsutils/objectValues';
import { ASTDefinitionBuilder } from './buildASTSchema';
import { GraphQLError } from '../error/GraphQLError';
import { isSchema, GraphQLSchema } from '../type/schema';
import { isIntrospectionType } from '../type/introspection';

import type { GraphQLSchemaValidationOptions } from '../type/schema';

import {
  isObjectType,
  isInterfaceType,
  isUnionType,
  isListType,
  isNonNullType,
  GraphQLList,
  GraphQLNonNull,
  GraphQLObjectType,
  GraphQLInterfaceType,
  GraphQLUnionType,
} from '../type/definition';

import { GraphQLDirective } from '../type/directives';

import { Kind } from '../language/kinds';

import type { GraphQLType, GraphQLNamedType } from '../type/definition';
import type {
  DocumentNode,
  DirectiveDefinitionNode,
  SchemaExtensionNode,
} from '../language/ast';

type Options = {|
  ...GraphQLSchemaValidationOptions,

  /**
   * Descriptions are defined as preceding string literals, however an older
   * experimental version of the SDL supported preceding comments as
   * descriptions. Set to true to enable this deprecated behavior.
   *
   * Default: false
   */
  commentDescriptions?: boolean,
|};

/**
 * Produces a new schema given an existing schema and a document which may
 * contain GraphQL type extensions and definitions. The original schema will
 * remain unaltered.
 *
 * Because a schema represents a graph of references, a schema cannot be
 * extended without effectively making an entire copy. We do not know until it's
 * too late if subgraphs remain unchanged.
 *
 * This algorithm copies the provided schema, applying extensions while
 * producing the copy. The original schema remains unaltered.
 *
 * Accepts options as a third argument:
 *
 *    - commentDescriptions:
 *        Provide true to use preceding comments as the description.
 *
 */
export function extendSchema(
  schema: GraphQLSchema,
  documentAST: DocumentNode,
  options?: Options,
): GraphQLSchema {
  invariant(isSchema(schema), 'Must provide valid GraphQLSchema');

  invariant(
    documentAST && documentAST.kind === Kind.DOCUMENT,
    'Must provide valid Document AST',
  );

  // Collect the type definitions and extensions found in the document.
  const typeDefinitionMap = Object.create(null);
  const typeExtensionsMap = Object.create(null);

  // New directives and types are separate because a directives and types can
  // have the same name. For example, a type named "skip".
  const directiveDefinitions: Array<DirectiveDefinitionNode> = [];

  // Schema extensions are collected which may add additional operation types.
  const schemaExtensions: Array<SchemaExtensionNode> = [];

  for (let i = 0; i < documentAST.definitions.length; i++) {
    const def = documentAST.definitions[i];
    switch (def.kind) {
      case Kind.SCHEMA_DEFINITION:
        // Sanity check that a schema extension is not defining a new schema
        throw new GraphQLError(
          'Cannot define a new schema within a schema extension.',
          [def],
        );
      case Kind.SCHEMA_EXTENSION:
        schemaExtensions.push(def);
        break;
      case Kind.OBJECT_TYPE_DEFINITION:
      case Kind.INTERFACE_TYPE_DEFINITION:
      case Kind.ENUM_TYPE_DEFINITION:
      case Kind.UNION_TYPE_DEFINITION:
      case Kind.SCALAR_TYPE_DEFINITION:
      case Kind.INPUT_OBJECT_TYPE_DEFINITION:
        // Sanity check that none of the defined types conflict with the
        // schema's existing types.
        const typeName = def.name.value;
        if (schema.getType(typeName)) {
          throw new GraphQLError(
            `Type "${typeName}" already exists in the schema. It cannot also ` +
              'be defined in this type definition.',
            [def],
          );
        }
        typeDefinitionMap[typeName] = def;
        break;
      case Kind.OBJECT_TYPE_EXTENSION:
      case Kind.INTERFACE_TYPE_EXTENSION:
        // Sanity check that this type extension exists within the
        // schema's existing types.
        const extendedTypeName = def.name.value;
        const existingType = schema.getType(extendedTypeName);
        if (!existingType) {
          throw new GraphQLError(
            `Cannot extend type "${extendedTypeName}" because it does not ` +
              'exist in the existing schema.',
            [def],
          );
        }
        checkExtensionNode(existingType, def);

        const existingTypeExtensions = typeExtensionsMap[extendedTypeName];
        typeExtensionsMap[extendedTypeName] = existingTypeExtensions
          ? existingTypeExtensions.concat([def])
          : [def];
        break;
      case Kind.DIRECTIVE_DEFINITION:
        const directiveName = def.name.value;
        const existingDirective = schema.getDirective(directiveName);
        if (existingDirective) {
          throw new GraphQLError(
            `Directive "${directiveName}" already exists in the schema. It ` +
              'cannot be redefined.',
            [def],
          );
        }
        directiveDefinitions.push(def);
        break;
      case Kind.SCALAR_TYPE_EXTENSION:
      case Kind.UNION_TYPE_EXTENSION:
      case Kind.ENUM_TYPE_EXTENSION:
      case Kind.INPUT_OBJECT_TYPE_EXTENSION:
        throw new Error(
          `The ${def.kind} kind is not yet supported by extendSchema().`,
        );
    }
  }

  // If this document contains no new types, extensions, or directives then
  // return the same unmodified GraphQLSchema instance.
  if (
    Object.keys(typeExtensionsMap).length === 0 &&
    Object.keys(typeDefinitionMap).length === 0 &&
    directiveDefinitions.length === 0 &&
    schemaExtensions.length === 0
  ) {
    return schema;
  }

  const astBuilder = new ASTDefinitionBuilder(
    typeDefinitionMap,
    options,
    typeRef => {
      const typeName = typeRef.name.value;
      const existingType = schema.getType(typeName);
      if (existingType) {
        return extendNamedType(existingType);
      }

      throw new GraphQLError(
        `Unknown type: "${typeName}". Ensure that this type exists ` +
          'either in the original schema, or is added in a type definition.',
        [typeRef],
      );
    },
  );

  const extendTypeCache = Object.create(null);

  // Get the extended root operation types.
  const operationTypes = {
    query: extendMaybeNamedType(schema.getQueryType()),
    mutation: extendMaybeNamedType(schema.getMutationType()),
    subscription: extendMaybeNamedType(schema.getSubscriptionType()),
  };

  // Then, incorporate all schema extensions.
  schemaExtensions.forEach(schemaExtension => {
    if (schemaExtension.operationTypes) {
      schemaExtension.operationTypes.forEach(operationType => {
        const operation = operationType.operation;
        if (operationTypes[operation]) {
          throw new Error(`Must provide only one ${operation} type in schema.`);
        }
        const typeRef = operationType.type;
        // Note: While this could make early assertions to get the correctly
        // typed values, that would throw immediately while type system
        // validation with validateSchema() will produce more actionable results.
        operationTypes[operation] = (astBuilder.buildType(typeRef): any);
      });
    }
  });

  const schemaExtensionASTNodes = schemaExtensions
    ? schema.extensionASTNodes
      ? schema.extensionASTNodes.concat(schemaExtensions)
      : schemaExtensions
    : schema.extensionASTNodes;

  const types = [
    // Iterate through all types, getting the type definition for each, ensuring
    // that any type not directly referenced by a field will get created.
    ...objectValues(schema.getTypeMap()).map(type => extendNamedType(type)),
    // Do the same with new types.
    ...astBuilder.buildTypes(objectValues(typeDefinitionMap)),
  ];

  // Support both original legacy names and extended legacy names.
  const schemaAllowedLegacyNames = schema.__allowedLegacyNames;
  const extendAllowedLegacyNames = options && options.allowedLegacyNames;
  const allowedLegacyNames =
    schemaAllowedLegacyNames && extendAllowedLegacyNames
      ? schemaAllowedLegacyNames.concat(extendAllowedLegacyNames)
      : schemaAllowedLegacyNames || extendAllowedLegacyNames;

  // Then produce and return a Schema with these types.
  return new GraphQLSchema({
    query: operationTypes.query,
    mutation: operationTypes.mutation,
    subscription: operationTypes.subscription,
    types,
    directives: getMergedDirectives(),
    astNode: schema.astNode,
    extensionASTNodes: schemaExtensionASTNodes,
    allowedLegacyNames,
  });

  // Below are functions used for producing this schema that have closed over
  // this scope and have access to the schema, cache, and newly defined types.

  function getMergedDirectives(): Array<GraphQLDirective> {
    const existingDirectives = schema.getDirectives();
    invariant(existingDirectives, 'schema must have default directives');

    return existingDirectives.concat(
      directiveDefinitions.map(node => astBuilder.buildDirective(node)),
    );
  }

  function extendMaybeNamedType<T: GraphQLNamedType>(type: ?T): ?T {
    return type ? extendNamedType(type) : null;
  }

  function extendNamedType<T: GraphQLNamedType>(type: T): T {
    if (isIntrospectionType(type)) {
      // Introspection types are not extended.
      return type;
    }

    const name = type.name;
    if (!extendTypeCache[name]) {
      if (isObjectType(type)) {
        extendTypeCache[name] = extendObjectType(type);
      } else if (isInterfaceType(type)) {
        extendTypeCache[name] = extendInterfaceType(type);
      } else if (isUnionType(type)) {
        extendTypeCache[name] = extendUnionType(type);
      } else {
        // This type is not yet extendable.
        extendTypeCache[name] = type;
      }
    }
    return (extendTypeCache[name]: any);
  }

  function extendObjectType(type: GraphQLObjectType): GraphQLObjectType {
    const name = type.name;
    const extensionASTNodes = typeExtensionsMap[name]
      ? type.extensionASTNodes
        ? type.extensionASTNodes.concat(typeExtensionsMap[name])
        : typeExtensionsMap[name]
      : type.extensionASTNodes;
    return new GraphQLObjectType({
      name,
      description: type.description,
      interfaces: () => extendImplementedInterfaces(type),
      fields: () => extendFieldMap(type),
      astNode: type.astNode,
      extensionASTNodes,
      isTypeOf: type.isTypeOf,
    });
  }

  function extendInterfaceType(
    type: GraphQLInterfaceType,
  ): GraphQLInterfaceType {
    const name = type.name;
    const extensionASTNodes = typeExtensionsMap[name]
      ? type.extensionASTNodes
        ? type.extensionASTNodes.concat(typeExtensionsMap[name])
        : typeExtensionsMap[name]
      : type.extensionASTNodes;
    return new GraphQLInterfaceType({
      name: type.name,
      description: type.description,
      fields: () => extendFieldMap(type),
      astNode: type.astNode,
      extensionASTNodes,
      resolveType: type.resolveType,
    });
  }

  function extendUnionType(type: GraphQLUnionType): GraphQLUnionType {
    return new GraphQLUnionType({
      name: type.name,
      description: type.description,
      types: type.getTypes().map(extendNamedType),
      astNode: type.astNode,
      resolveType: type.resolveType,
    });
  }

  function extendImplementedInterfaces(
    type: GraphQLObjectType,
  ): Array<GraphQLInterfaceType> {
    const interfaces = type.getInterfaces().map(extendNamedType);

    // If there are any extensions to the interfaces, apply those here.
    const extensions = typeExtensionsMap[type.name];
    if (extensions) {
      extensions.forEach(extension => {
        extension.interfaces.forEach(namedType => {
          // Note: While this could make early assertions to get the correctly
          // typed values, that would throw immediately while type system
          // validation with validateSchema() will produce more actionable results.
          interfaces.push((astBuilder.buildType(namedType): any));
        });
      });
    }

    return interfaces;
  }

  function extendFieldMap(type: GraphQLObjectType | GraphQLInterfaceType) {
    const newFieldMap = Object.create(null);
    const oldFieldMap = type.getFields();
    Object.keys(oldFieldMap).forEach(fieldName => {
      const field = oldFieldMap[fieldName];
      newFieldMap[fieldName] = {
        description: field.description,
        deprecationReason: field.deprecationReason,
        type: extendType(field.type),
        args: keyMap(field.args, arg => arg.name),
        astNode: field.astNode,
        resolve: field.resolve,
      };
    });

    // If there are any extensions to the fields, apply those here.
    const extensions = typeExtensionsMap[type.name];
    if (extensions) {
      extensions.forEach(extension => {
        extension.fields.forEach(field => {
          const fieldName = field.name.value;
          if (oldFieldMap[fieldName]) {
            throw new GraphQLError(
              `Field "${type.name}.${fieldName}" already exists in the ` +
                'schema. It cannot also be defined in this type extension.',
              [field],
            );
          }
          newFieldMap[fieldName] = astBuilder.buildField(field);
        });
      });
    }

    return newFieldMap;
  }

  function extendType<T: GraphQLType>(typeDef: T): T {
    if (isListType(typeDef)) {
      return (GraphQLList(extendType(typeDef.ofType)): any);
    }
    if (isNonNullType(typeDef)) {
      return (GraphQLNonNull(extendType(typeDef.ofType)): any);
    }
    return extendNamedType(typeDef);
  }
}

function checkExtensionNode(type, node) {
  switch (node.kind) {
    case Kind.OBJECT_TYPE_EXTENSION:
      if (!isObjectType(type)) {
        throw new GraphQLError(
          `Cannot extend non-object type "${type.name}".`,
          [node],
        );
      }
      break;
    case Kind.INTERFACE_TYPE_EXTENSION:
      if (!isInterfaceType(type)) {
        throw new GraphQLError(
          `Cannot extend non-interface type "${type.name}".`,
          [node],
        );
      }
      break;
  }
}
<|endoftext|>