<commit_before>import React from 'react'
import Portal from 'react-portal'
import position from './caret-position'
import SuggestionItem from './suggestion-item'
import getCurrentWord from './current-word'
import {
    UP_ARROW_KEY,
    DOWN_ARROW_KEY,
    RESULT_SIZE,
} from './constants'

class SuggestionPortal extends React.Component {
    // Adjust position on initial mount
    componentDidMount = () => {
        this.adjustPosition()
    }

    // Adjust position on each update
    componentDidUpdate = () => {
        this.adjustPosition()
    }

    constructor(props) {
        super()
        // Set callback values so we can pass functions up
        props.callback.onKeyDown = this.onKeyDown
        props.callback.onEnter = props.onEnter
        props.callback.closePortal = this.closePortal
        props.callback.readOnly = false

        // Storing selected index in state b/c updates should trigger a re-render
        this.state = {
            selectedIndex: 0,
        }

        // Set first suggestion
        if (typeof props.suggestions === 'function') {
            const filteredSuggestions = props.suggestions('');
            props.callback.suggestion = filteredSuggestions[this.state.selectedIndex]
        } else {
            const filteredSuggestions = props.suggestions.slice(0, props.resultSize ? props.resultSize : RESULT_SIZE)
            props.callback.suggestion = filteredSuggestions[this.state.selectedIndex]
        }
    }

    // Given # positions changed, update selectedIndex with new position
    setSelectedIndex = (newIndex) => {
        this.setState({
            selectedIndex: newIndex
        });
    }

    // Use filtered suggestions and index to pass a suggestion up to plugin
    setCallbackSuggestion = (filteredSuggestions, selectedIndex=0) => {
        if (filteredSuggestions.length) {
            this.props.callback.suggestion = filteredSuggestions[selectedIndex]
        } else {
            this.props.callback.suggestion = undefined
        }
    }

    // Given # positions changed, return new index, wrapping if overflow
    getNewMenuPostion = (change) => {
        const filteredSuggestions  = this.getFilteredSuggestions();
        if(filteredSuggestions.length === 0) {
            return 0;
        } else {
            // this will allow wrap around to the end of the list
            const changePlusOriginalLength = change + filteredSuggestions.length;
            const changeAfterWrapping = (this.state.selectedIndex + changePlusOriginalLength) % filteredSuggestions.length;
            return changeAfterWrapping;
        }
    }

    // Use new key-presses to update the current suggestion
    onKeyDown = (keyCode, data) => {
        if (keyCode === DOWN_ARROW_KEY || keyCode === UP_ARROW_KEY) {
            // If up/down, change position in list
            const filteredSuggestions  = this.getFilteredSuggestions();
            const positionChange = (keyCode === DOWN_ARROW_KEY) ? +1 : -1;
            const newIndex = this.getNewMenuPostion(positionChange);
            this.setSelectedIndex(newIndex)
            this.setCallbackSuggestion(filteredSuggestions, newIndex);
        } else {
            // Else, determine character and update suggestions accordingly
            const newFilteredSuggestions  = this.getFilteredSuggestions(data);
            this.setSelectedIndex(0)
            if (typeof newFilteredSuggestions.then === 'function') {
                newFilteredSuggestions.then(newFilteredSuggestions => {
                    this.setCallbackSuggestion(newFilteredSuggestions, 0);
                }).catch(() => {
                    this.setCallbackSuggestion([]);
                })
            } else {
                this.setCallbackSuggestion(newFilteredSuggestions, 0);
            }
        }
    }

    // Determines if we've hit a trigger-char
    matchTrigger = () => {
        const { state, trigger, startOfParagraph } = this.props
        // Only match if the state is focuses and not expanded
        const stateCondition = state.isFocused && !state.isExpanded

        // Selection has no anchor, we have no text: ergo no match
        if (!state.selection.anchorKey) return false

        const { anchorText, anchorOffset } = state
        if (startOfParagraph) {
            // Matched if all the text in this block(paragraph) matches the trigger
            // and if the state condition is good
            return stateCondition && anchorText.text === trigger
        }

        // Else check f the last char is a match for the trigger
        const lastChar = anchorText.text[anchorOffset - 1]
        return stateCondition && lastChar && lastChar === trigger
    }

    // Get the match text if there is any
    matchCapture = () => {
        const { state, capture, trigger } = this.props

        // Selection has no anchor, we have no text: ergo no match
        if (!state.selection.anchorKey) return ''

        // Else, compare current word to he
        const { anchorText, anchorOffset } = state
        const currentWord = getCurrentWord(anchorText.text, anchorOffset - 1, trigger)
        const text = this.getMatchText(currentWord, capture)
        return text
    }

    // Get the match text if there is any
    getMatchText = (text, trigger) => {
        const matchArr = text.match(trigger)
        if (matchArr) {
            return matchArr[1].toLowerCase()
        }
        return undefined
    }

    // Filter suggestions based on incoming data
    getFilteredSuggestions = (incomingData) => {
        const { suggestions, state, capture, resultSize, trigger } = this.props

        if (!state.selection.anchorKey) return [];

        const { anchorText, anchorOffset } = state;

        let nextChar = "";
        // If there is incoming data from a keydown, include that as next char
        if (incomingData !== undefined) {
            nextChar = this.convertSlateDataObjectToCharacter(incomingData);
            if (nextChar == null) return [];
        }

        // Put together newText based on nextCharacter; change offset if char is -
        let newText = anchorText.text;
        let offset = anchorOffset;
        if (nextChar === "backspace") {
            if (newText.length > 0) {
                // Remove last character if we've backspaced and there is a letter
                newText = newText.slice(0, newText.length - 1);
                offset -= 1;
            }
        } else {
            // Else, add the processed character
            newText += nextChar;
        }

        // Get the current word after processing the new data
        const currentWord = getCurrentWord(newText, offset, trigger);
        const text = this.getMatchText(currentWord, capture)

        if (typeof suggestions === 'function') {
            return suggestions(text)
        } else {
            const filtered = suggestions
                .filter(suggestion => suggestion.key.toLowerCase().indexOf(text) !== -1)
                .slice(0, resultSize ? resultSize : RESULT_SIZE);
            return filtered;
        }
    }

    setDefaultSuggestion = () => {
        const { suggestions, resultSize, trigger } = this.props
        // Set first suggestion
        let filteredSuggestions;
        if (typeof suggestions === 'function') {
            filteredSuggestions = suggestions('');
        } else {
            filteredSuggestions = suggestions.slice(0, resultSize ? resultSize : RESULT_SIZE)
        }
        this.setCallbackSuggestion(filteredSuggestions, 0);
    }

    // Turn event data into characters to match against
    convertSlateDataObjectToCharacter = (data) => {
        const code = data.code;
        const isShift = data.isShift;
        if (code === 8) return "backspace";
        if (code < 48) return null;
        if (code < 58) { // number keys
            if (isShift) {
                if (code === 48) return ")";
                if (code === 49) return "!";
                if (code === 50) return "@";
                if (code === 51) return "#";
                if (code === 52) return "$";
                if (code === 53) return "%";
                if (code === 54) return "^";
                if (code === 55) return "&";
                if (code === 56) return "*";
                if (code === 57) return "(";
            }
            return String.fromCharCode(code);
        }
        if (code >= 65 && code <= 90) { // A-Z, a-z
            if (isShift) return String.fromCharCode(code);
            return String.fromCharCode(code + 32);
        }
        if (code >= 96 && code <= 105) return String.fromCharCode(code - 48); // numpad 0-9
        if (code === 187 && !isShift) return "=";
        if (code === 188 && !isShift) return ",";
        if (code === 189 && !isShift) return '-';
        if (code === 190 && !isShift) return ".";
        if (code === 191 && !isShift) return "/";
        if (code === 187 && isShift) return "+";
        if (code === 188 && isShift) return "<";
        if (code === 189 && isShift) return '_';
        if (code === 190 && isShift) return ">";
        if (code === 191 && isShift) return "?";
        return null;
    }

    // Adjust menu styling and position when needed
    adjustPosition = () => {
        const { menu } = this.state;
        // If there is no menu, return
        if (!menu) return;

        const match = this.matchCapture();
        if (match === undefined) {
            // No match: remove menu styling
            menu.removeAttribute('style');
            return;
        }

        if (this.matchTrigger() || match) {
            const rect = position()
            if (!rect) {
                menu.removeAttribute('style');
                menu.style.display = 'none'
            } else {
                menu.style.display = 'block'
                menu.style.opacity = 1
                menu.style.top = `${rect.top + window.pageYOffset}px` // eslint-disable-line no-mixed-operators
                menu.style.left = `${rect.left + window.pageXOffset}px` // eslint-disable-line no-mixed-operators
            }
        }
    }

    // Assigns a value to menu when the portal opens
    openPortal = (portal) => {
        this.setState({
            menu: portal.firstChild
        });
    }

    // Closes portal
    closePortal = () => {
        const { menu } = this.state;
        console.log('----- closing portal')
        console.log(menu)
        // No menu to close: return
        if (!menu) return;

        // Remove menu styling
        menu.removeAttribute('style');
        // Reset default suggestion for elements
        this.setDefaultSuggestion();
        return;
    }

    render = () => {
        const filteredSuggestions  = this.getFilteredSuggestions();

        return (
            <Portal isOpened onOpen={this.openPortal}>

                        {filteredSuggestions.map((suggestion, index) =>
                            <SuggestionItem
                                key={suggestion.key}
                                index={index}
                                suggestion={suggestion}
                                selectedIndex={this.state.selectedIndex}
                                setSelectedIndex={this.setSelectedIndex}
                                appendSuggestion={this.props.callback.onEnter}
                                closePortal={this.closePortal}
                                editor={this.props.callback.editor}
                            />
                        )}

            </Portal>
        );
    }
}

export default SuggestionPortal
<commit_msg>Added in changes for repositioning back in after rebase<commit_after>import React from 'react'
import Portal from 'react-portal'
import position from './caret-position'
import SuggestionItem from './suggestion-item'
import getCurrentWord from './current-word'
import {
    UP_ARROW_KEY,
    DOWN_ARROW_KEY,
    RESULT_SIZE,
} from './constants'

class SuggestionPortal extends React.Component {
    // Adjust position on initial mount
    componentDidMount = () => {
        this.adjustPosition()
    }

    // Adjust position on each update
    componentDidUpdate = () => {
        this.adjustPosition()
    }

    constructor(props) {
        super()
        // Set callback values so we can pass functions up
        props.callback.onKeyDown = this.onKeyDown
        props.callback.onEnter = props.onEnter
        props.callback.closePortal = this.closePortal
        props.callback.readOnly = false

        // Storing selected index in state b/c updates should trigger a re-render
        this.state = {
            selectedIndex: 0,
        }

        // Set first suggestion
        if (typeof props.suggestions === 'function') {
            const filteredSuggestions = props.suggestions('');
            props.callback.suggestion = filteredSuggestions[this.state.selectedIndex]
        } else {
            const filteredSuggestions = props.suggestions.slice(0, props.resultSize ? props.resultSize : RESULT_SIZE)
            props.callback.suggestion = filteredSuggestions[this.state.selectedIndex]
        }
    }

    // Given # positions changed, update selectedIndex with new position
    setSelectedIndex = (newIndex) => {
        this.setState({
            selectedIndex: newIndex
        });
    }

    // Use filtered suggestions and index to pass a suggestion up to plugin
    setCallbackSuggestion = (filteredSuggestions, selectedIndex=0) => {
        if (filteredSuggestions.length) {
            this.props.callback.suggestion = filteredSuggestions[selectedIndex]
        } else {
            this.props.callback.suggestion = undefined
        }
    }

    // Given # positions changed, return new index, wrapping if overflow
    getNewMenuPostion = (change) => {
        const filteredSuggestions  = this.getFilteredSuggestions();
        if(filteredSuggestions.length === 0) {
            return 0;
        } else {
            // this will allow wrap around to the end of the list
            const changePlusOriginalLength = change + filteredSuggestions.length;
            const changeAfterWrapping = (this.state.selectedIndex + changePlusOriginalLength) % filteredSuggestions.length;
            return changeAfterWrapping;
        }
    }

    // Use new key-presses to update the current suggestion
    onKeyDown = (keyCode, data) => {
        if (keyCode === DOWN_ARROW_KEY || keyCode === UP_ARROW_KEY) {
            const height = this.refs.suggestionPortal.offsetHeight;
            const numberOfElementsVisible = Math.floor(height/32);
            // If up/down, change position in list
            const filteredSuggestions  = this.getFilteredSuggestions();
            const positionChange = (keyCode === DOWN_ARROW_KEY) ? +1 : -1;
            const newIndex = this.getNewMenuPostion(positionChange);
            this.setSelectedIndex(newIndex)
            this.setCallbackSuggestion(filteredSuggestions, newIndex);
            // newIndex - (numberOfElementsVisible - 1) forces the scrolling to happen once your reach the bottom of the list in view.
            // 32 is the height of each suggestion in the list, 10 allows for the margin
            this.refs.suggestionPortal.scrollTop = (newIndex - (numberOfElementsVisible - 1)) * 32 + 10;
        } else {
            // Else, determine character and update suggestions accordingly
            const newFilteredSuggestions  = this.getFilteredSuggestions(data);
            this.setSelectedIndex(0)
            if (typeof newFilteredSuggestions.then === 'function') {
                newFilteredSuggestions.then(newFilteredSuggestions => {
                    this.setCallbackSuggestion(newFilteredSuggestions, 0);
                }).catch(() => {
                    this.setCallbackSuggestion([]);
                })
            } else {
                this.setCallbackSuggestion(newFilteredSuggestions, 0);
            }
        }
    }

    // Determines if we've hit a trigger-char
    matchTrigger = () => {
        const { state, trigger, startOfParagraph } = this.props
        // Only match if the state is focuses and not expanded
        const stateCondition = state.isFocused && !state.isExpanded

        // Selection has no anchor, we have no text: ergo no match
        if (!state.selection.anchorKey) return false

        const { anchorText, anchorOffset } = state
        if (startOfParagraph) {
            // Matched if all the text in this block(paragraph) matches the trigger
            // and if the state condition is good
            return stateCondition && anchorText.text === trigger
        }

        // Else check f the last char is a match for the trigger
        const lastChar = anchorText.text[anchorOffset - 1]
        return stateCondition && lastChar && lastChar === trigger
    }

    // Get the match text if there is any
    matchCapture = () => {
        const { state, capture, trigger } = this.props

        // Selection has no anchor, we have no text: ergo no match
        if (!state.selection.anchorKey) return ''

        // Else, compare current word to he
        const { anchorText, anchorOffset } = state
        const currentWord = getCurrentWord(anchorText.text, anchorOffset - 1, trigger)
        const text = this.getMatchText(currentWord, capture)
        return text
    }

    // Get the match text if there is any
    getMatchText = (text, trigger) => {
        const matchArr = text.match(trigger)
        if (matchArr) {
            return matchArr[1].toLowerCase()
        }
        return undefined
    }

    // Filter suggestions based on incoming data
    getFilteredSuggestions = (incomingData) => {
        const { suggestions, state, capture, resultSize, trigger } = this.props

        if (!state.selection.anchorKey) return [];

        const { anchorText, anchorOffset } = state;

        let nextChar = "";
        // If there is incoming data from a keydown, include that as next char
        if (incomingData !== undefined) {
            nextChar = this.convertSlateDataObjectToCharacter(incomingData);
            if (nextChar == null) return [];
        }

        // Put together newText based on nextCharacter; change offset if char is -
        let newText = anchorText.text;
        let offset = anchorOffset;
        if (nextChar === "backspace") {
            if (newText.length > 0) {
                // Remove last character if we've backspaced and there is a letter
                newText = newText.slice(0, newText.length - 1);
                offset -= 1;
            }
        } else {
            // Else, add the processed character
            newText += nextChar;
        }

        // Get the current word after processing the new data
        const currentWord = getCurrentWord(newText, offset, trigger);
        const text = this.getMatchText(currentWord, capture)

        if (typeof suggestions === 'function') {
            return suggestions(text)
        } else {
            const filtered = suggestions
                .filter(suggestion => suggestion.key.toLowerCase().indexOf(text) !== -1)
                .slice(0, resultSize ? resultSize : RESULT_SIZE);
            return filtered;
        }
    }

    setDefaultSuggestion = () => {
        const { suggestions, resultSize, trigger } = this.props
        // Set first suggestion
        let filteredSuggestions;
        if (typeof suggestions === 'function') {
            filteredSuggestions = suggestions('');
        } else {
            filteredSuggestions = suggestions.slice(0, resultSize ? resultSize : RESULT_SIZE)
        }
        this.setCallbackSuggestion(filteredSuggestions, 0);
    }

    // Turn event data into characters to match against
    convertSlateDataObjectToCharacter = (data) => {
        const code = data.code;
        const isShift = data.isShift;
        if (code === 8) return "backspace";
        if (code < 48) return null;
        if (code < 58) { // number keys
            if (isShift) {
                if (code === 48) return ")";
                if (code === 49) return "!";
                if (code === 50) return "@";
                if (code === 51) return "#";
                if (code === 52) return "$";
                if (code === 53) return "%";
                if (code === 54) return "^";
                if (code === 55) return "&";
                if (code === 56) return "*";
                if (code === 57) return "(";
            }
            return String.fromCharCode(code);
        }
        if (code >= 65 && code <= 90) { // A-Z, a-z
            if (isShift) return String.fromCharCode(code);
            return String.fromCharCode(code + 32);
        }
        if (code >= 96 && code <= 105) return String.fromCharCode(code - 48); // numpad 0-9
        if (code === 187 && !isShift) return "=";
        if (code === 188 && !isShift) return ",";
        if (code === 189 && !isShift) return '-';
        if (code === 190 && !isShift) return ".";
        if (code === 191 && !isShift) return "/";
        if (code === 187 && isShift) return "+";
        if (code === 188 && isShift) return "<";
        if (code === 189 && isShift) return '_';
        if (code === 190 && isShift) return ">";
        if (code === 191 && isShift) return "?";
        return null;
    }

    // Adjust menu styling and position when needed
    adjustPosition = () => {
        const { menu } = this.state;
        // If there is no menu, return
        if (!menu) return;

        const match = this.matchCapture();
        if (match === undefined) {
            // No match: remove menu styling
            menu.removeAttribute('style');
            return;
        }

        if (this.matchTrigger() || match) {
            const rect = position()
            if (!rect) {
                menu.removeAttribute('style');
                menu.style.display = 'none'
            } else {
                menu.style.display = 'block'
                menu.style.opacity = 1
                if (window.innerHeight - rect.top < 230) {
                    menu.style.bottom = `${window.innerHeight - rect.top - window.pageYOffset}px` // eslint-disable-line no-mixed-operators
                    menu.style.left = `${rect.left + window.pageXOffset + 10}px` // eslint-disable-line no-mixed-operators
                } else {
                    menu.style.top = `${rect.top + window.pageYOffset}px` // eslint-disable-line no-mixed-operators
                    menu.style.left = `${rect.left + window.pageXOffset}px` // eslint-disable-line no-mixed-operators
                }
            }
        }
    }

    // Assigns a value to menu when the portal opens
    openPortal = (portal) => {
        this.setState({
            menu: portal.firstChild
        });
    }

    // Closes portal
    closePortal = () => {
        const { menu } = this.state;
        console.log('----- closing portal')
        console.log(menu)
        // No menu to close: return
        if (!menu) return;

        // Remove menu styling
        menu.removeAttribute('style');
        // Reset default suggestion for elements
        this.setDefaultSuggestion();
        return;
    }

    render = () => {
        const filteredSuggestions  = this.getFilteredSuggestions();

        return (
            <Portal isOpened onOpen={this.openPortal}>

                        {filteredSuggestions.map((suggestion, index) =>
                            <SuggestionItem
                                key={suggestion.key}
                                index={index}
                                suggestion={suggestion}
                                selectedIndex={this.state.selectedIndex}
                                setSelectedIndex={this.setSelectedIndex}
                                appendSuggestion={this.props.callback.onEnter}
                                closePortal={this.closePortal}
                                editor={this.props.callback.editor}
                            />
                        )}

            </Portal>
        );
    }
}

export default SuggestionPortal
<|endoftext|>


<commit_before>/*    Copyright 2021 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

const log = require('../net2/logger.js')(__filename);

const Sensor = require('./Sensor.js').Sensor;

const _ = require('lodash');

const validator = require('validator');

const IntelTool = require('../net2/IntelTool');
const intelTool = new IntelTool();

const rclient = require('../util/redis_manager.js').getRedisClient();
const sclient = require('../util/redis_manager.js').getSubscriptionClient()

const m = require('../extension/metrics/metrics.js');

const flowUtil = require('../net2/FlowUtil');
const f = require('../net2/Firewalla.js');

const userConfigFolder = f.getUserConfigFolder();
const dnsmasqConfigFolder = `${userConfigFolder}/dnsmasq`;

const cc = require('../extension/cloudcache/cloudcache.js');

const zlib = require('zlib');
const fs = require('fs');

const Promise = require('bluebird');
const inflateAsync = Promise.promisify(zlib.inflate);
Promise.promisifyAll(fs);

const DNSMASQ = require('../extension/dnsmasq/dnsmasq.js');
const dnsmasq = new DNSMASQ();

const Alarm = require('../alarm/Alarm.js');
const AlarmManager2 = require('../alarm/AlarmManager2.js')
const am2 = new AlarmManager2();
const HostTool = require('../net2/HostTool.js')
const hostTool = new HostTool()
const BF_SERVER_MATCH = "bf_server_match"
const IdentityManager = require('../net2/IdentityManager.js');
const sem = require('../sensor/SensorEventManager.js').getInstance();

const sys = require('sys'),
      Buffer = require('buffer').Buffer,
      dgram = require('dgram');

// slices a single byte into bits
// assuming only single bytes
const sliceBits = function(b, off, len) {
    const s = 7 - (off + len - 1);

    b = b >>> s;
    return b & ~(0xff << len);
};

const qnameToDomain = (qname) => {
  let domain= '';
  for(let i=0;i<qname.length;i++) {
    if (qname[i] == 0) {
      //last char chop trailing .
      domain = domain.substring(0, domain.length - 1);
      break;
    }

    const tmpBuf = qname.slice(i+1, i+qname[i]+1);
    domain += tmpBuf.toString('binary', 0, tmpBuf.length);
    domain += '.';

    i = i + qname[i];
  }

  return domain;
};

const defaultExpireTime = 48 * 3600; // expire in two days, by default
const allowKey = "dns_proxy:allow_list"; //unused at this moment
const passthroughKey = "dns_proxy:passthrough_list";
const blockKey = "dns_proxy:block_list";
const featureName = "dns_proxy";
const defaultListenPort = 9963;

class DNSProxyPlugin extends Sensor {
  async run() {
    // invalidate cache keys when starting up
    await rclient.delAsync(allowKey);
    await rclient.delAsync(blockKey);
    await rclient.delAsync(passthroughKey);

    this.hookFeature(featureName);
  }

  getHashKeyName(item = {}) {
    if(!item.count || !item.error || !item.prefix) {
      log.error("Invalid item:", item);
      return null;
    }

    const {count, error, prefix} = item;

    return `bf:${prefix}:${count}:${error}`;
  }

  getFilePath(item = {}) {
    if(!item.count || !item.error || !item.prefix) {
      log.error("Invalid item:", item);
      return null;
    }

    const {count, error, prefix} = item;

    return `${f.getRuntimeInfoFolder()}/${featureName}.${prefix}.bf.data`;
  }

  getDnsmasqConfigFile() {
    return `${dnsmasqConfigFolder}/${featureName}.conf`;
  }

  async enableDnsmasqConfig() {
    const data = this.config.data || [];

    let dnsmasqEntry = "mac-address-tag=%FF:FF:FF:FF:FF:FF$dns_proxy\n";

    for(const item of data) {
      const fp = this.getFilePath(item);
      if(!fp) {
        continue;
      }

      const entry = `server-bf-high=<${fp},${item.count},${item.error}><${allowKey}><${blockKey}><${passthroughKey}>127.0.0.1#${this.getPort()}$dns_proxy\n`;

      dnsmasqEntry += entry;
    }

    await fs.writeFileAsync(this.getDnsmasqConfigFile(), dnsmasqEntry);
    await dnsmasq.scheduleRestartDNSService();
  }

  async disableDnsmasqConfig() {
    await fs.unlinkAsync(this.getDnsmasqConfigFile());
    await dnsmasq.scheduleRestartDNSService();
  }

  async globalOn() {
    sclient.subscribe(BF_SERVER_MATCH)
    sclient.on("message", async (channel, message) => {

      switch(channel) {
        case BF_SERVER_MATCH:
          let msgObj;
          try {
            msgObj = JSON.parse(message)
          } catch(err) {
            log.error("parse msg failed", err, message)
          }
          if (msgObj && msgObj.mac) {
            const MAC = msgObj.mac.toUpperCase();
            const ip = msgObj.ip4 || msgObj.ip6;
            await this.processRequest(msgObj.domain, ip, MAC);
          }
          break;
      }
    })

    sem.on("FastDNSPolicyComplete", async (event) => {
      await rclient.saddAsync(passthroughKey, event.domain);
      await rclient.sremAsync(blockKey, event.domain);
    })

    const data = this.config.data || [];
    if(_.isEmpty(data)) {
      return;
    }

    for(const item of data) {
      const hashKeyName = this.getHashKeyName(item);
      if(!hashKeyName) continue;

      try {
        await cc.enableCache(hashKeyName, (data) => {
          this.updateBFData(item, data);
        });
      } catch(err) {
        log.error("Failed to process bf data:", item);
      }
    }

    await this.enableDnsmasqConfig();
  }

  async updateBFData(item, content) {
    try {
      if(!content || content.length < 10) {
        // likely invalid, return null for protection
        log.error(`Invalid bf data content for ${item && item.prefix}, ignored`);
        return;
      }
      const buf = Buffer.from(content, 'base64');
      const output = await inflateAsync(buf);
      const fp = this.getFilePath(item);
      if(!fp) return;

      await fs.writeFileAsync(fp, output);
    } catch(err) {
      log.error("Failed to update bf data, err:", err);
    }
  }

  async globalOff() {
    sclient.unsubscribe(BF_SERVER_MATCH)
    await cc.disableCache(this.getHashKeyName());
    await this.disableDnsmasqConfig();
  }

  getPort() {
    const port = this.config.listenPort || defaultListenPort;

    if(!_.isNumber(port)) {
      log.warn("Invalid port", port, ", reverting back to default port.");
      return defaultListenPort;
    }

    return port;

  }

  async checkCache(domain) { // only return if there is exact-match intel in redis
    return intelTool.getDomainIntel(domain);
  }

  async _genSecurityAlarm(ip, mac, dn, item) {
    let macEntry;
    if (mac) macEntry = await hostTool.getMACEntry(mac);
    if (macEntry) {
      ip = macEntry.ipv4 || macEntry.ipv6 || ip;
    } else {
      if (!ip) return;
      let m;
      const identity = IdentityManager.getIdentityByIP(ip);
      if (identity) m = IdentityManager.getGUID(identity);
      if (!m) return;
      mac = m;
    }
    const alarm = new Alarm.IntelAlarm(new Date() / 1000, ip, "major", {
      "p.device.ip": ip,
      "p.dest.name": dn,
      "p.security.reason": item.msg,
      "p.device.mac": mac,
      "p.action.block": true,
      "p.blockby": "fastdns"
    });
    await am2.enrichDeviceInfo(alarm)
    try {
      await am2.checkAndSaveAsync(alarm);
    } catch (err) {
      if (err.code !== 'ERR_DUP_ALARM' && err.code !== 'ERR_BLOCKED_BY_POLICY_ALREADY') {
        throw new Error("fail to gen fastdns block alarm", err);
      }
    }
  }

  async processRequest(qname, ip, mac) {
    const domain = qname;
    log.info("dns request is", domain);
    const begin = new Date() / 1;
    const cache = await this.checkCache(domain);
    if(cache) {
      log.info(`inteldns:${domain} is already cached locally, updating redis cache keys directly...`);
      if (cache.c === "intel") {
        await this._genSecurityAlarm(ip, mac, domain, cache)
      } else {
        await rclient.saddAsync(passthroughKey, domain);
        await rclient.sremAsync(blockKey, domain);
      }
      return; // do need to do anything
    }
    const result = await intelTool.checkIntelFromCloud(undefined, domain); // parameter ip is undefined since it's a dns request only
    await this.updateCache(domain, result, ip, mac);
    const end = new Date() / 1;
    log.info("dns intel result is", result, "took", Math.floor(end - begin), "ms");
  }

  async updateCache(domain, result, ip, mac) {
    if(_.isEmpty(result)) { // empty intel, means the domain is good
      const domains = flowUtil.getSubDomains(domain);
      if(!domains) {
        log.warn("Invalid Domain", domain, "skipped.");
        return;
      }

      // since result is empty, it means all sub domains of this domain are good
      const placeholder = {c: 'x', a: '1'};
      for(const dn of domains) {
        await intelTool.addDomainIntel(dn, placeholder, defaultExpireTime);
      }

      // only the exact dn is added to the passthrough and block list
      // generic domains can't be added, because another sub domain may be malicous
      // example:
      //   domain1.blogspot.com may be good
      //   and domain2.blogspot.com may be malicous
      // when domain1 is checked to be good, we should NOT add blogspot.com to passthrough_list
      await rclient.saddAsync(passthroughKey, domain);
      await rclient.sremAsync(blockKey, domain);

    } else {
      let skipped = false;
      for(const item of result) {
        const dn = item.originIP; // originIP is the domain
        const isDomain = validator.isFQDN(dn);
        if(!isDomain) {
          continue;
        }

        for(const k in item) { // to suppress redis error
          const v = item[k];
          if(_.isBoolean(v) || _.isNumber(v) || _.isString(v)) {
            continue;
          }
          item[k] = JSON.stringify(v);
        }

        if (item.c === "intel" && !skipped) {
          await this._genSecurityAlarm(ip, mac, dn, item)
          skipped = true
        } else {
          await rclient.saddAsync(passthroughKey, dn);
          await rclient.sremAsync(blockKey, dn);
        }
        await intelTool.addDomainIntel(dn, item, item.e || defaultExpireTime);

      }
    }
  }

}

module.exports = DNSProxyPlugin;
<commit_msg>bug fix on disable cache<commit_after>/*    Copyright 2021 Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

const log = require('../net2/logger.js')(__filename);

const Sensor = require('./Sensor.js').Sensor;

const _ = require('lodash');

const validator = require('validator');

const IntelTool = require('../net2/IntelTool');
const intelTool = new IntelTool();

const rclient = require('../util/redis_manager.js').getRedisClient();
const sclient = require('../util/redis_manager.js').getSubscriptionClient()

const m = require('../extension/metrics/metrics.js');

const flowUtil = require('../net2/FlowUtil');
const f = require('../net2/Firewalla.js');

const userConfigFolder = f.getUserConfigFolder();
const dnsmasqConfigFolder = `${userConfigFolder}/dnsmasq`;

const cc = require('../extension/cloudcache/cloudcache.js');

const zlib = require('zlib');
const fs = require('fs');

const Promise = require('bluebird');
const inflateAsync = Promise.promisify(zlib.inflate);
Promise.promisifyAll(fs);

const DNSMASQ = require('../extension/dnsmasq/dnsmasq.js');
const dnsmasq = new DNSMASQ();

const Alarm = require('../alarm/Alarm.js');
const AlarmManager2 = require('../alarm/AlarmManager2.js')
const am2 = new AlarmManager2();
const HostTool = require('../net2/HostTool.js')
const hostTool = new HostTool()
const BF_SERVER_MATCH = "bf_server_match"
const IdentityManager = require('../net2/IdentityManager.js');
const sem = require('../sensor/SensorEventManager.js').getInstance();

const sys = require('sys'),
      Buffer = require('buffer').Buffer,
      dgram = require('dgram');

// slices a single byte into bits
// assuming only single bytes
const sliceBits = function(b, off, len) {
    const s = 7 - (off + len - 1);

    b = b >>> s;
    return b & ~(0xff << len);
};

const qnameToDomain = (qname) => {
  let domain= '';
  for(let i=0;i<qname.length;i++) {
    if (qname[i] == 0) {
      //last char chop trailing .
      domain = domain.substring(0, domain.length - 1);
      break;
    }

    const tmpBuf = qname.slice(i+1, i+qname[i]+1);
    domain += tmpBuf.toString('binary', 0, tmpBuf.length);
    domain += '.';

    i = i + qname[i];
  }

  return domain;
};

const defaultExpireTime = 48 * 3600; // expire in two days, by default
const allowKey = "dns_proxy:allow_list"; //unused at this moment
const passthroughKey = "dns_proxy:passthrough_list";
const blockKey = "dns_proxy:block_list";
const featureName = "dns_proxy";
const defaultListenPort = 9963;

class DNSProxyPlugin extends Sensor {
  async run() {
    // invalidate cache keys when starting up
    await rclient.delAsync(allowKey);
    await rclient.delAsync(blockKey);
    await rclient.delAsync(passthroughKey);

    this.hookFeature(featureName);
  }

  getHashKeyName(item = {}) {
    if(!item.count || !item.error || !item.prefix) {
      log.error("Invalid item:", item);
      return null;
    }

    const {count, error, prefix} = item;

    return `bf:${prefix}:${count}:${error}`;
  }

  getFilePath(item = {}) {
    if(!item.count || !item.error || !item.prefix) {
      log.error("Invalid item:", item);
      return null;
    }

    const {count, error, prefix} = item;

    return `${f.getRuntimeInfoFolder()}/${featureName}.${prefix}.bf.data`;
  }

  getDnsmasqConfigFile() {
    return `${dnsmasqConfigFolder}/${featureName}.conf`;
  }

  async enableDnsmasqConfig() {
    const data = this.config.data || [];

    let dnsmasqEntry = "mac-address-tag=%FF:FF:FF:FF:FF:FF$dns_proxy\n";

    for(const item of data) {
      const fp = this.getFilePath(item);
      if(!fp) {
        continue;
      }

      const entry = `server-bf-high=<${fp},${item.count},${item.error}><${allowKey}><${blockKey}><${passthroughKey}>127.0.0.1#${this.getPort()}$dns_proxy\n`;

      dnsmasqEntry += entry;
    }

    await fs.writeFileAsync(this.getDnsmasqConfigFile(), dnsmasqEntry);
    await dnsmasq.scheduleRestartDNSService();
  }

  async disableDnsmasqConfig() {
    await fs.unlinkAsync(this.getDnsmasqConfigFile());
    await dnsmasq.scheduleRestartDNSService();
  }

  async globalOn() {
    sclient.subscribe(BF_SERVER_MATCH)
    sclient.on("message", async (channel, message) => {

      switch(channel) {
        case BF_SERVER_MATCH:
          let msgObj;
          try {
            msgObj = JSON.parse(message)
          } catch(err) {
            log.error("parse msg failed", err, message)
          }
          if (msgObj && msgObj.mac) {
            const MAC = msgObj.mac.toUpperCase();
            const ip = msgObj.ip4 || msgObj.ip6;
            await this.processRequest(msgObj.domain, ip, MAC);
          }
          break;
      }
    })

    sem.on("FastDNSPolicyComplete", async (event) => {
      await rclient.saddAsync(passthroughKey, event.domain);
      await rclient.sremAsync(blockKey, event.domain);
    })

    const data = this.config.data || [];
    if(_.isEmpty(data)) {
      return;
    }

    for(const item of data) {
      const hashKeyName = this.getHashKeyName(item);
      if(!hashKeyName) continue;

      try {
        await cc.enableCache(hashKeyName, (data) => {
          this.updateBFData(item, data);
        });
      } catch(err) {
        log.error("Failed to process bf data:", item);
      }
    }

    await this.enableDnsmasqConfig();
  }

  async updateBFData(item, content) {
    try {
      if(!content || content.length < 10) {
        // likely invalid, return null for protection
        log.error(`Invalid bf data content for ${item && item.prefix}, ignored`);
        return;
      }
      const buf = Buffer.from(content, 'base64');
      const output = await inflateAsync(buf);
      const fp = this.getFilePath(item);
      if(!fp) return;

      await fs.writeFileAsync(fp, output);
    } catch(err) {
      log.error("Failed to update bf data, err:", err);
    }
  }

  async globalOff() {
    sclient.unsubscribe(BF_SERVER_MATCH)

    const data = this.config.data || [];
    if(!_.isEmpty(data)) {
      for(const item of data) {
        const hashKeyName = this.getHashKeyName(item);
        if(!hashKeyName) continue;
        await cc.disableCache(this.getHashKeyName()).catch((err) => {
          log.error("Failed to disable cache, err:", err);
        });
      }
    }

    await this.disableDnsmasqConfig();
  }

  getPort() {
    const port = this.config.listenPort || defaultListenPort;

    if(!_.isNumber(port)) {
      log.warn("Invalid port", port, ", reverting back to default port.");
      return defaultListenPort;
    }

    return port;

  }

  async checkCache(domain) { // only return if there is exact-match intel in redis
    return intelTool.getDomainIntel(domain);
  }

  async _genSecurityAlarm(ip, mac, dn, item) {
    let macEntry;
    if (mac) macEntry = await hostTool.getMACEntry(mac);
    if (macEntry) {
      ip = macEntry.ipv4 || macEntry.ipv6 || ip;
    } else {
      if (!ip) return;
      let m;
      const identity = IdentityManager.getIdentityByIP(ip);
      if (identity) m = IdentityManager.getGUID(identity);
      if (!m) return;
      mac = m;
    }
    const alarm = new Alarm.IntelAlarm(new Date() / 1000, ip, "major", {
      "p.device.ip": ip,
      "p.dest.name": dn,
      "p.security.reason": item.msg,
      "p.device.mac": mac,
      "p.action.block": true,
      "p.blockby": "fastdns"
    });
    await am2.enrichDeviceInfo(alarm)
    try {
      await am2.checkAndSaveAsync(alarm);
    } catch (err) {
      if (err.code !== 'ERR_DUP_ALARM' && err.code !== 'ERR_BLOCKED_BY_POLICY_ALREADY') {
        throw new Error("fail to gen fastdns block alarm", err);
      }
    }
  }

  async processRequest(qname, ip, mac) {
    const domain = qname;
    log.info("dns request is", domain);
    const begin = new Date() / 1;
    const cache = await this.checkCache(domain);
    if(cache) {
      log.info(`inteldns:${domain} is already cached locally, updating redis cache keys directly...`);
      if (cache.c === "intel") {
        await this._genSecurityAlarm(ip, mac, domain, cache)
      } else {
        await rclient.saddAsync(passthroughKey, domain);
        await rclient.sremAsync(blockKey, domain);
      }
      return; // do need to do anything
    }
    const result = await intelTool.checkIntelFromCloud(undefined, domain); // parameter ip is undefined since it's a dns request only
    await this.updateCache(domain, result, ip, mac);
    const end = new Date() / 1;
    log.info("dns intel result is", result, "took", Math.floor(end - begin), "ms");
  }

  async updateCache(domain, result, ip, mac) {
    if(_.isEmpty(result)) { // empty intel, means the domain is good
      const domains = flowUtil.getSubDomains(domain);
      if(!domains) {
        log.warn("Invalid Domain", domain, "skipped.");
        return;
      }

      // since result is empty, it means all sub domains of this domain are good
      const placeholder = {c: 'x', a: '1'};
      for(const dn of domains) {
        await intelTool.addDomainIntel(dn, placeholder, defaultExpireTime);
      }

      // only the exact dn is added to the passthrough and block list
      // generic domains can't be added, because another sub domain may be malicous
      // example:
      //   domain1.blogspot.com may be good
      //   and domain2.blogspot.com may be malicous
      // when domain1 is checked to be good, we should NOT add blogspot.com to passthrough_list
      await rclient.saddAsync(passthroughKey, domain);
      await rclient.sremAsync(blockKey, domain);

    } else {
      let skipped = false;
      for(const item of result) {
        const dn = item.originIP; // originIP is the domain
        const isDomain = validator.isFQDN(dn);
        if(!isDomain) {
          continue;
        }

        for(const k in item) { // to suppress redis error
          const v = item[k];
          if(_.isBoolean(v) || _.isNumber(v) || _.isString(v)) {
            continue;
          }
          item[k] = JSON.stringify(v);
        }

        if (item.c === "intel" && !skipped) {
          await this._genSecurityAlarm(ip, mac, dn, item)
          skipped = true
        } else {
          await rclient.saddAsync(passthroughKey, dn);
          await rclient.sremAsync(blockKey, dn);
        }
        await intelTool.addDomainIntel(dn, item, item.e || defaultExpireTime);

      }
    }
  }

}

module.exports = DNSProxyPlugin;
<|endoftext|>


<commit_before>//==================================================
// 数据缓存模块
//==================================================
$.define("data", "lang", function(){
    //$.log("已加载data模块");
    var remitter = /object|function/, rbrace = /^(?:\{.*\}|\[.*\])$/;
    $.mix( $, {
        _db:{},
        // 读写数据
        data : function( target, name, data, pvt ) {
            if(target && remitter.test(typeof target)){//只处理HTML节点与普通对象
                var id = $.getUid(target), isEl = target.nodeType === 1
                if(name === "@uuid"){
                    return id;
                }
                var database = isEl ? $._db: target,
                table = database[ "@data_"+id ] || (database[ "@data_"+id ] = {}),
                _table =  table.data || (table.data = {});
                if(isEl && !table.parsedAttrs){
                    var attrs = target.attributes;
                    //将HTML5单一的字符串数据转化为mass多元化的数据
                    for ( var i = 0, attr, val; attr = attrs[i++];) {
                        name = attr.name, val = attr.value
                        if ( name.indexOf( "data-" ) === 0 && typeof  value == "string" ) {
                            try {
                                data = val === "true" ? true :
                                val === "false" ? false :
                                val === "null" ? null :
                                isFinite( val ) ? +val :
                                rbrace.test( val ) ? $.parseJSON( val ) :
                                val;
                            } catch( e ) {
                                val = undefined
                            }
                            _table[ name + id ] = val
                        }
                    }
                    table.parsedAttrs = true;
                }
                if ( !pvt ) {
                    table = _table;
                }
                var getByName = typeof name === "string";
                if ( name && typeof name == "object" ) {
                    $.mix(table, name);
                }else if(getByName && data !== void 0){
                    table[ name ] = data;
                }
                return getByName ? table[ name + id ] || table[ name ] : table;
            }
        },//仅内部调用
        _data:function(target,name,data){
            return $.data(target, name, data, true)
        },
        //移除数据
        removeData : function(target, name, pvt){
            if(target && remitter.test(typeof target)){
                var id =  $.getUid(target);
                if (  !id ) {
                    return;
                }
                var  clear = 1, ret = typeof name == "string",
                database = target.nodeType === 1  ? $._db : target,
                table = database["@data_"+id] ;
                if ( table && ret ) {
                    if(!pvt){
                        table = table.data
                    }
                    if(table){
                        ret = table[ name ];
                        delete table[ name ];
                    }
                    var cache = database["@data_"+id];
                        loop:
                        for(var key in cache){
                            if(key == "data"){
                                for(var i in cache.data){
                                    clear = 0;
                                    break loop;
                                }
                            }else{
                                clear = 0;
                                break loop;
                            }
                        }
                }
                if(clear){
                    delete database["@data_"+id];
                }
                return ret;
            }
        },
        //合并数据
        mergeData:function(neo, src){
            var srcData = $._data(src), neoData = $._data(neo), events = srcData.events;
            if(srcData && neoData){
                $.Object.merge( neoData, srcData );
                if(events){
                    delete neoData.callback;
                    neoData.events = {};
                    for ( var type in events ) {
                        for (var i = 0, obj ; obj =  events[ type ][i++]; ) {
                            $.event.bind.call( neo, obj );
                        }
                    }
                }
            }
        }
    });
});

/**
2011.9.27 uniqueID改为uniqueNumber 简化data与removeData
2011.9.28 添加$._data处理内部数据
2011.10.21 强化mergeData，可以拷贝事件
2012.1.31 简化$.Object.merge的调用
2012.4.5 修正mergeData BUG, 让$.data能获取HTML5 data-*
*/
<commit_msg>修正mergeData BUG, 让$.data能获取HTML5 data-*<commit_after>//==================================================
// 数据缓存模块
//==================================================
$.define("data", "lang", function(){
    //$.log("已加载data模块");
    var remitter = /object|function/, rbrace = /^(?:\{.*\}|\[.*\])$/;
    $.mix( $, {
        _db: {},
        // 读写数据
        data : function( target, name, data, pvt ) {
            if(target && remitter.test(typeof target)){//只处理HTML节点与普通对象
                var id = $.getUid(target), isEl = target.nodeType === 1
                if(name === "@uuid"){
                    return id;
                }
                var database = isEl ? $._db: target,
                table = database[ "@data_"+id ] || (database[ "@data_"+id ] = {}),
                _table =  table.data || (table.data = {});
                if(isEl && !table.parsedAttrs){
                    var attrs = target.attributes;
                    //将HTML5单一的字符串数据转化为mass多元化的数据
                    for ( var i = 0, attr, val; attr = attrs[i++];) {
                        name = attr.name, val = attr.value
                        if ( name.indexOf( "data-" ) === 0 && typeof  value == "string" ) {
                            try {
                                data = val === "true" ? true :
                                val === "false" ? false :
                                val === "null" ? null :
                                isFinite( val ) ? +val :
                                rbrace.test( val ) ? $.parseJSON( val ) :
                                val;
                            } catch( e ) {
                                val = undefined
                            }
                            _table[ name + id ] = val
                        }
                    }
                    table.parsedAttrs = true;
                }
                if ( !pvt ) {
                    table = _table;
                }
                var getByName = typeof name === "string";
                if ( name && typeof name == "object" ) {
                    $.mix(table, name);
                }else if(getByName && data !== void 0){
                    table[ name ] = data;
                }
                return getByName ? table[ name + id ] || table[ name ] : table;
            }
        },//仅内部调用
        _data:function(target,name,data){
            return $.data(target, name, data, true)
        },
        //移除数据
        removeData : function(target, name, pvt){
            if(target && remitter.test(typeof target)){
                var id =  $.getUid(target);
                if (  !id ) {
                    return;
                }
                var  clear = 1, ret = typeof name == "string",
                database = target.nodeType === 1  ? $._db : target,
                table = database["@data_"+id] ;
                if ( table && ret ) {
                    if(!pvt){
                        table = table.data
                    }
                    if(table){
                        ret = table[ name ];
                        delete table[ name ];
                    }
                    var cache = database["@data_"+id];
                        loop:
                        for(var key in cache){
                            if(key == "data"){
                                for(var i in cache.data){
                                    clear = 0;
                                    break loop;
                                }
                            }else{
                                clear = 0;
                                break loop;
                            }
                        }
                }
                if(clear){
                    delete database["@data_"+id];
                }
                return ret;
            }
        },
        //合并数据
        mergeData:function(neo, src){
            var srcData = $._data(src), neoData = $._data(neo), events = srcData.events;
            if(srcData && neoData){
                $.Object.merge( neoData, srcData );
                if(events){
                    delete neoData.callback;
                    neoData.events = {};
                    for ( var type in events ) {
                        for (var i = 0, obj ; obj =  events[ type ][i++]; ) {
                            $.event.bind.call( neo, obj );
                        }
                    }
                }
            }
        }
    });
});

/**
2011.9.27 uniqueID改为uniqueNumber 简化data与removeData
2011.9.28 添加$._data处理内部数据
2011.10.21 强化mergeData，可以拷贝事件
2012.1.31 简化$.Object.merge的调用
2012.4.5 修正mergeData BUG, 让$.data能获取HTML5 data-*
*/
<|endoftext|>


<commit_before>// sample.js : シューティングゲーム
//
// See : http://mikkun.github.io/gacco/ga028/study02/
//
// Written by KUSANAGI Mitsuhisa <mikkun@mbg.nifty.com> / Date : 2015-03-29

"use strict";

var SCREEN_WIDTH = 320,
    SCREEN_HEIGHT = 480,
    BG_COLOR = "rgb(0, 0, 0)",
    FONT_FACE = "bold 12px monospace",
    FONT_COLOR = "rgb(255, 255, 255)",

    MAX_BG_IMAGE = 2,
    MAX_SHOT = 3,
    MAX_ENEMY = 8,
    MAX_BEAM = 8,

    WAIT = 10,
    SCORE_LIMIT = 100000000,

    background = new Image(),
    sprite = new Image(),

    bg_image = {},
    bg_images = [],
    player_point,
    player = {},
    shot = {},
    shots = [],
    enemy = {},
    enemies = [],
    beam = {},
    beams = [];

// オブジェクトを継承
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function setup() {
    var i;

    document.title = "EVADE AND DESTROY";
    document.getElementsByName("viewport")[0].content
        = "width=" + SCREEN_WIDTH;

    screenWidth = SCREEN_WIDTH;
    screenHeight = SCREEN_HEIGHT;
    pbCanvas.width = SCREEN_WIDTH;
    pbCanvas.height = SCREEN_HEIGHT;
    pbCanvas.style.backgroundColor = BG_COLOR;

    background.src = "./img/background.png";
    sprite.src = "./img/sprite.png";

    // 背景
    bg_image = {
        id: 0,
        y: SCREEN_HEIGHT,
        dy: 1,
        scroll: function () { // 背景スクロール
            this.y = this.y >= 0 ? this.y : SCREEN_HEIGHT;
            pbCtx.drawImage(
                background,
                320 * this.id, this.y, SCREEN_WIDTH, SCREEN_HEIGHT,
                0, 0, SCREEN_WIDTH, SCREEN_HEIGHT
            );
        }
    };
    for (i = 0; i < MAX_BG_IMAGE; i += 1) {
        bg_images[i] = object(bg_image);
        bg_images[i].id = i;
        bg_images[i].dy = Math.pow(2, i);
    }

    // プレイヤー
    player_point = new PhysicalPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT - 64);
    player = {
        score: 0,
        time: 0,
        id: 0,
        x: player_point.x,
        y: player_point.y,
        velocity: 24,
        is_alive: true,
        is_gameover: false,
        pattern: 0,
        move: function () { // プレイヤー移動
            var sprite_x;
            this.x = this.x <                16 ?                16
                   : this.x > SCREEN_WIDTH - 16 ? SCREEN_WIDTH - 16
                   :                              this.x;
            switch (this.id) {
            case 0: // 自機
                this.pattern = this.pattern === 0 ? 1 : 0;
                sprite_x = 0 + 32 * this.pattern;
                break;
            case 251: // 爆発パターン#1
                sprite_x = 160;
                break;
            case 252: // 爆発パターン#2
                sprite_x = 192;
                break;
            case 253: // 爆発パターン#3
                sprite_x = 224;
                break;
            case 254: // 爆発パターン#4
                sprite_x = 256;
                break;
            default: // 爆発パターン消去
                this.y = 32767;
            }
            pbCtx.drawImage(
                sprite,
                sprite_x, 0, 32, 32,
                this.x - 16, this.y - 16, 32, 32
            );
        }
    };

    // 自機ショット
    shot = {
        x: -32768,
        y: player_point.y,
        dy: 16,
        is_alive: false,
        pattern: 0,
        move: function () { // 自機ショット移動
            this.y = this.y >= 0 ? this.y : -32768;
            this.is_alive = this.y >= 8 ? true : false;
            this.pattern = this.pattern === 0 ? 1 : 0;
            pbCtx.drawImage(
                sprite,
                288, (0 + 16 * this.pattern), 16, 16,
                this.x - 8, this.y - 8, 16, 16
            );
        }
    };
    for (i = 0; i < MAX_SHOT; i += 1) {
        shots[i] = object(shot);
    }

    // 敵キャラクター
    enemy = {
        id: 0,
        pts: 0,
        x: 32767,
        y: 32767,
        dx: 2,
        dy: 1,
        dy_default: 1,
        is_alive: false,
        pattern: 0,
        move: function () { // 敵キャラクター移動
            var sprite_x;
            this.y = this.y <= SCREEN_HEIGHT ? this.y : 32767;
            this.dy
                = this.y <= SCREEN_HEIGHT - 16 ? this.dy : this.dy_default;
            this.is_alive
                = this.y >= 0 && this.y <= SCREEN_HEIGHT - 16 ? true : false;
            switch (this.id) {
            case 0: // 敵機
                this.pts = 1000;
                this.dy = 3;
                this.dy_default = 3;
                this.pattern = this.pattern === 0 ? 1 : 0;
                sprite_x = 64 + 32 * this.pattern;
                break;
            case 1: // 隕石
                this.dy_default = 1;
                sprite_x = 128;
                break;
            case 251: // 爆発パターン#1
                this.dy = 2;
                sprite_x = 160;
                break;
            case 252: // 爆発パターン#2
                this.dy = 2;
                sprite_x = 192;
                break;
            case 253: // 爆発パターン#3
                this.dy = 1;
                sprite_x = 224;
                break;
            case 254: // 爆発パターン#4
                this.dy = 0;
                sprite_x = 256;
                break;
            default: // 爆発パターン消去
                this.y = 32767;
            }
            pbCtx.drawImage(
                sprite,
                sprite_x, 0, 32, 32,
                this.x - 16, this.y - 16, 32, 32
            );
        },
        checkCollision: function () { // 敵キャラクター衝突判定
            var n, dx, dy;
            if (!this.is_alive) {
                return;
            }
            if (player.is_alive) { // 対自機
                dx = this.x - player.x;
                dy = this.y - player.y;
                if (Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) < 24) {
                    player.id = 251;
                    player.is_alive = false;
                }
            }
            for (n = 0; n < MAX_SHOT; n += 1) { // 対自機ショット
                if (!shots[n].is_alive || !player.is_alive) {
                    continue;
                }
                dx = this.x - shots[n].x;
                dy = this.y - shots[n].y;
                if (Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) < 16) {
                    player.score += this.id === 0 ? this.pts : 0;
                    this.id = this.id === 0 ? 251 : this.id;
                    this.is_alive = this.id >= 251 ? false : this.is_alive;
                    shots[n].y = this.is_alive ? -32768 : shots[n].y;
                }
            }
        }
    };
    for (i = 0; i < MAX_ENEMY; i += 1) {
        enemies[i] = object(enemy);
    }

    // 敵機ビーム
    beam = {
        x: 32767,
        y: 32767,
        dy: 8,
        is_alive: false,
        pattern: 0,
        move: function () { // 敵機ビーム移動
            this.y = this.y <= SCREEN_HEIGHT ? this.y : 32767;
            this.is_alive
                = this.y >= 0 && this.y <= SCREEN_HEIGHT - 16 ? true : false;
            this.pattern = this.pattern === 0 ? 1 : 0;
            pbCtx.drawImage(
                sprite,
                304, (0 + 16 * this.pattern), 16, 16,
                this.x - 8, this.y - 8, 16, 16
            );
        },
        checkCollision: function () { // 敵機ビーム衝突判定
            var dx, dy;
            if (!this.is_alive) {
                return;
            }
            if (player.is_alive) { // 対自機
                dx = this.x - player.x;
                dy = this.y - player.y;
                if (Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) < 8) {
                    player.id = 251;
                    player.is_alive = false;
                }
            }
        }
    };
    for (i = 0; i < MAX_BEAM; i += 1) {
        beams[i] = object(beam);
    }
}

function loop() {
    var i, j;

    pbCtx.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

    // 背景
    for (i = 0; i < MAX_BG_IMAGE; i += 1) {
        bg_images[i].y -= bg_images[i].dy;
        bg_images[i].scroll();
    }

    // 自機ショット
    for (i = 0; i < MAX_SHOT; i += 1) {
        if (!player.is_alive) {
            break;
        }
        if (curYubiY >= SCREEN_HEIGHT - 48) { // 自機ショット発射
            shots[i].x = shots[i].y < 0 ? player.x + 1 : shots[i].x;
            shots[i].y = shots[i].y < 0 ? player.y + 16 : shots[i].y;
            for (j = 0; j < MAX_SHOT; j += 1) {
                if (i !== j && shots[j].y === shots[i].y) {
                    shots[i].y -= 8;
                }
            }
        }
        shots[i].y -= shots[i].dy;
        shots[i].move();
    }

    // プレイヤー
    if (player.is_alive) {
        if (curYubiTouched) { // プレイヤー移動
            player_point.setKasokudo(
                (curYubiX - player_point.x) * player.velocity, 0
            );
        }
        player.x = player_point.x;
    } else {
        if (player.id >= 251 && player.id <= 254 + WAIT) { // 爆発パターン
            player.id += 1;
        }
        if (player.id === 255 + WAIT) { // ゲームオーバー
            player.is_gameover = true;
        }
        if (player.is_gameover && curYubiTouched) { // 再スタート
            location.reload();
        }
    }
    player.move();

    // 敵キャラクター
    for (i = 0; i < MAX_ENEMY; i += 1) {
        if (enemies[i].y > SCREEN_HEIGHT - 16) {
            enemies[i].id = Math.floor(Math.random() * 2);
            enemies[i].x = 16 + 32 * Math.floor(Math.random() * 10);
            enemies[i].y
                = -SCREEN_WIDTH + Math.floor(Math.random() * SCREEN_WIDTH);
        }
        if (enemies[i].id === 0 && Math.abs(player.x - enemies[i].x) <= 96) {
            for (j = 0; j < MAX_BEAM; j += 1) { // 敵機ビーム発射
                if (beams[j].is_alive
                    || !(enemies[i].y >= 32 && enemies[i].y <= player.y - 64)
                    || Math.floor(Math.random() * 256) !== 0) {
                    continue;
                }
                beams[j].x = enemies[i].x + 1;
                beams[j].y = enemies[i].y + 16;
                break;
            }
        }
        if (enemies[i].id === 0 && player.y - enemies[i].y <= 256) { // 敵機
            enemies[i].x
                = player.x - enemies[i].x > 48 ? enemies[i].x + enemies[i].dx
                : enemies[i].x - player.x > 48 ? enemies[i].x - enemies[i].dx
                :                                enemies[i].x;
            enemies[i].dy *= 2;
        }
        if (enemies[i].id === 1 && enemies[i].y < 0) { // 隕石
            enemies[i].dy = 1 + Math.floor(Math.random() * 3);
        }
        if (enemies[i].id >= 251 && enemies[i].id <= 254) { // 爆発パターン
            enemies[i].id += 1;
        }
        enemies[i].y += enemies[i].dy;
        enemies[i].move();
        enemies[i].checkCollision();
    }

    // 敵機ビーム
    for (i = 0; i < MAX_BEAM; i += 1) {
        beams[i].y += beams[i].dy;
        beams[i].move();
        beams[i].checkCollision();
    }

    // 自機ショット用タップエリア
    pbCtx.drawImage(
        sprite,
        0, 32, 320, 32,
        0, SCREEN_HEIGHT - 32, 320, 32
    );

    // 得点表示欄
    pbCtx.beginPath();
    pbCtx.fillStyle = BG_COLOR;
    pbCtx.rect(0, 0, SCREEN_WIDTH, 16);
    pbCtx.fill();

    // 得点表示
    pbCtx.font = FONT_FACE;
    pbCtx.fillStyle = FONT_COLOR;
    if (player.is_alive) {
        player.score += 1;
        player.score
            = player.score > SCORE_LIMIT ? SCORE_LIMIT : player.score;
    } else {
        pbCtx.fillText("===== GAME  OVER =====", SCREEN_WIDTH / 2, 12);
        pbCtx.fillText("--- TAP TO RESTART ---", SCREEN_WIDTH / 2, 24);
    }
    pbCtx.fillText("SCORE: " + player.score, 2, 12);
}

function onPressed(n) {}

function onMove(n) {}

function onReleased(n) {}
<commit_msg>再スタートの処理をlocation.reload()からsetup()に変更<commit_after>// sample.js : シューティングゲーム「EVADE AND DESTROY」
//
// See : http://mikkun.github.io/gacco/ga028/study02/
//
// Written by KUSANAGI Mitsuhisa <mikkun@mbg.nifty.com> / Date : 2015-03-30

"use strict";

var SCREEN_WIDTH = 320,
    SCREEN_HEIGHT = 480,
    BG_COLOR = "rgb(0, 0, 0)",
    FONT_FACE = "bold 12px monospace",
    FONT_COLOR = "rgb(255, 255, 255)",

    MAX_BG_IMAGE = 2,
    MAX_SHOT = 3,
    MAX_ENEMY = 8,
    MAX_BEAM = 8,

    WAIT = 10,
    SCORE_LIMIT = 100000000,

    background = new Image(),
    sprite = new Image(),

    bg_image = {},
    bg_images = [],
    player_point,
    player = {},
    shot = {},
    shots = [],
    enemy = {},
    enemies = [],
    beam = {},
    beams = [];

// オブジェクトを継承
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function setup() {
    var i;

    document.title = "EVADE AND DESTROY";
    document.getElementsByName("viewport")[0].content
        = "width=" + SCREEN_WIDTH;
    document.body.style.backgroundColor = BG_COLOR;

    screenWidth = SCREEN_WIDTH;
    screenHeight = SCREEN_HEIGHT;
    pbCanvas.width = SCREEN_WIDTH;
    pbCanvas.height = SCREEN_HEIGHT;
    pbCanvas.style.backgroundColor = BG_COLOR;

    background.src = "./img/background.png";
    sprite.src = "./img/sprite.png";

    // 背景
    bg_image = {
        id: 0,
        y: SCREEN_HEIGHT,
        dy: 1,
        scroll: function () { // 背景スクロール
            this.y = this.y >= 0 ? this.y : SCREEN_HEIGHT;
            pbCtx.drawImage(
                background,
                320 * this.id, this.y, SCREEN_WIDTH, SCREEN_HEIGHT,
                0, 0, SCREEN_WIDTH, SCREEN_HEIGHT
            );
        }
    };
    for (i = 0; i < MAX_BG_IMAGE; i += 1) {
        bg_images[i] = object(bg_image);
        bg_images[i].id = i;
        bg_images[i].dy = Math.pow(2, i);
    }

    // プレイヤー
    player_point = new PhysicalPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT - 64);
    player = {
        score: 0,
        time: 0,
        id: 0,
        x: player_point.x,
        y: player_point.y,
        velocity: 24,
        is_alive: true,
        is_gameover: false,
        pattern: 0,
        move: function () { // プレイヤー移動
            var sprite_x;
            this.x = this.x <                16 ?                16
                   : this.x > SCREEN_WIDTH - 16 ? SCREEN_WIDTH - 16
                   :                              this.x;
            switch (this.id) {
            case 0: // 自機
                this.pattern = this.pattern === 0 ? 1 : 0;
                sprite_x = 0 + 32 * this.pattern;
                break;
            case 251: // 爆発パターン#1
                sprite_x = 160;
                break;
            case 252: // 爆発パターン#2
                sprite_x = 192;
                break;
            case 253: // 爆発パターン#3
                sprite_x = 224;
                break;
            case 254: // 爆発パターン#4
                sprite_x = 256;
                break;
            default: // 爆発パターン消去
                this.y = 32767;
            }
            pbCtx.drawImage(
                sprite,
                sprite_x, 0, 32, 32,
                this.x - 16, this.y - 16, 32, 32
            );
        }
    };

    // 自機ショット
    shot = {
        x: -32768,
        y: player_point.y,
        dy: 16,
        is_alive: false,
        pattern: 0,
        move: function () { // 自機ショット移動
            this.y = this.y >= 0 ? this.y : -32768;
            this.is_alive = this.y >= 8 ? true : false;
            this.pattern = this.pattern === 0 ? 1 : 0;
            pbCtx.drawImage(
                sprite,
                288, (0 + 16 * this.pattern), 16, 16,
                this.x - 8, this.y - 8, 16, 16
            );
        }
    };
    for (i = 0; i < MAX_SHOT; i += 1) {
        shots[i] = object(shot);
    }

    // 敵キャラクター
    enemy = {
        id: 0,
        pts: 0,
        x: 32767,
        y: 32767,
        dx: 2,
        dy: 1,
        dy_default: 1,
        is_alive: false,
        pattern: 0,
        move: function () { // 敵キャラクター移動
            var sprite_x;
            this.y = this.y <= SCREEN_HEIGHT ? this.y : 32767;
            this.dy
                = this.y <= SCREEN_HEIGHT - 16 ? this.dy : this.dy_default;
            this.is_alive
                = this.y >= 0 && this.y <= SCREEN_HEIGHT - 16 ? true : false;
            switch (this.id) {
            case 0: // 敵機
                this.pts = 1000;
                this.dy = 3;
                this.dy_default = 3;
                this.pattern = this.pattern === 0 ? 1 : 0;
                sprite_x = 64 + 32 * this.pattern;
                break;
            case 1: // 隕石
                this.dy_default = 1;
                sprite_x = 128;
                break;
            case 251: // 爆発パターン#1
                this.dy = 2;
                sprite_x = 160;
                break;
            case 252: // 爆発パターン#2
                this.dy = 2;
                sprite_x = 192;
                break;
            case 253: // 爆発パターン#3
                this.dy = 1;
                sprite_x = 224;
                break;
            case 254: // 爆発パターン#4
                this.dy = 0;
                sprite_x = 256;
                break;
            default: // 爆発パターン消去
                this.y = 32767;
            }
            pbCtx.drawImage(
                sprite,
                sprite_x, 0, 32, 32,
                this.x - 16, this.y - 16, 32, 32
            );
        },
        checkCollision: function () { // 敵キャラクター衝突判定
            var n, dx, dy;
            if (!this.is_alive) {
                return;
            }
            if (player.is_alive) { // 対自機
                dx = this.x - player.x;
                dy = this.y - player.y;
                if (Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) < 24) {
                    player.id = 251;
                    player.is_alive = false;
                }
            }
            for (n = 0; n < MAX_SHOT; n += 1) { // 対自機ショット
                if (!shots[n].is_alive || !player.is_alive) {
                    continue;
                }
                dx = this.x - shots[n].x;
                dy = this.y - shots[n].y;
                if (Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) < 16) {
                    player.score += this.id === 0 ? this.pts : 0;
                    this.id = this.id === 0 ? 251 : this.id;
                    this.is_alive = this.id >= 251 ? false : this.is_alive;
                    shots[n].y = this.is_alive ? -32768 : shots[n].y;
                }
            }
        }
    };
    for (i = 0; i < MAX_ENEMY; i += 1) {
        enemies[i] = object(enemy);
    }

    // 敵機ビーム
    beam = {
        x: 32767,
        y: 32767,
        dy: 8,
        is_alive: false,
        pattern: 0,
        move: function () { // 敵機ビーム移動
            this.y = this.y <= SCREEN_HEIGHT ? this.y : 32767;
            this.is_alive
                = this.y >= 0 && this.y <= SCREEN_HEIGHT - 16 ? true : false;
            this.pattern = this.pattern === 0 ? 1 : 0;
            pbCtx.drawImage(
                sprite,
                304, (0 + 16 * this.pattern), 16, 16,
                this.x - 8, this.y - 8, 16, 16
            );
        },
        checkCollision: function () { // 敵機ビーム衝突判定
            var dx, dy;
            if (!this.is_alive) {
                return;
            }
            if (player.is_alive) { // 対自機
                dx = this.x - player.x;
                dy = this.y - player.y;
                if (Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) < 8) {
                    player.id = 251;
                    player.is_alive = false;
                }
            }
        }
    };
    for (i = 0; i < MAX_BEAM; i += 1) {
        beams[i] = object(beam);
    }
}

function loop() {
    var i, j;

    pbCtx.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

    // 背景
    for (i = 0; i < MAX_BG_IMAGE; i += 1) {
        bg_images[i].y -= bg_images[i].dy;
        bg_images[i].scroll();
    }

    // 自機ショット
    for (i = 0; i < MAX_SHOT; i += 1) {
        if (!player.is_alive) {
            break;
        }
        if (curYubiY >= SCREEN_HEIGHT - 48) { // 自機ショット発射
            shots[i].x = shots[i].y < 0 ? player.x + 1 : shots[i].x;
            shots[i].y = shots[i].y < 0 ? player.y + 16 : shots[i].y;
            for (j = 0; j < MAX_SHOT; j += 1) {
                if (i !== j && shots[j].y === shots[i].y) {
                    shots[i].y -= 8;
                }
            }
        }
        shots[i].y -= shots[i].dy;
        shots[i].move();
    }

    // プレイヤー
    if (player.is_alive) {
        if (curYubiTouched) { // プレイヤー移動
            player_point.setKasokudo(
                (curYubiX - player_point.x) * player.velocity, 0
            );
        }
        player.x = player_point.x;
    } else {
        if (player.id >= 251 && player.id <= 254 + WAIT) { // 爆発パターン
            player.id += 1;
        }
        if (player.id === 255 + WAIT) { // ゲームオーバー
            player.is_gameover = true;
        }
        if (player.is_gameover && curYubiTouched) { // 再スタート
            setup();
        }
    }
    player.move();

    // 敵キャラクター
    for (i = 0; i < MAX_ENEMY; i += 1) {
        if (enemies[i].y > SCREEN_HEIGHT - 16) {
            enemies[i].id = Math.floor(Math.random() * 2);
            enemies[i].x = 16 + 32 * Math.floor(Math.random() * 10);
            enemies[i].y
                = -SCREEN_WIDTH + Math.floor(Math.random() * SCREEN_WIDTH);
        }
        if (enemies[i].id === 0 && Math.abs(player.x - enemies[i].x) <= 96) {
            for (j = 0; j < MAX_BEAM; j += 1) { // 敵機ビーム発射
                if (beams[j].is_alive
                    || !(enemies[i].y >= 32 && enemies[i].y <= player.y - 64)
                    || Math.floor(Math.random() * 256) !== 0) {
                    continue;
                }
                beams[j].x = enemies[i].x + 1;
                beams[j].y = enemies[i].y + 16;
                break;
            }
        }
        if (enemies[i].id === 0 && player.y - enemies[i].y <= 256) { // 敵機
            enemies[i].x
                = player.x - enemies[i].x > 48 ? enemies[i].x + enemies[i].dx
                : enemies[i].x - player.x > 48 ? enemies[i].x - enemies[i].dx
                :                                enemies[i].x;
            enemies[i].dy *= 2;
        }
        if (enemies[i].id === 1 && enemies[i].y < 0) { // 隕石
            enemies[i].dy = 1 + Math.floor(Math.random() * 3);
        }
        if (enemies[i].id >= 251 && enemies[i].id <= 254) { // 爆発パターン
            enemies[i].id += 1;
        }
        enemies[i].y += enemies[i].dy;
        enemies[i].move();
        enemies[i].checkCollision();
    }

    // 敵機ビーム
    for (i = 0; i < MAX_BEAM; i += 1) {
        beams[i].y += beams[i].dy;
        beams[i].move();
        beams[i].checkCollision();
    }

    // 自機ショット用タップエリア
    pbCtx.drawImage(
        sprite,
        0, 32, 320, 32,
        0, SCREEN_HEIGHT - 32, 320, 32
    );

    // 得点表示欄
    pbCtx.beginPath();
    pbCtx.fillStyle = BG_COLOR;
    pbCtx.rect(0, 0, SCREEN_WIDTH, 16);
    pbCtx.fill();

    // 得点表示
    pbCtx.font = FONT_FACE;
    pbCtx.fillStyle = FONT_COLOR;
    if (player.is_alive) {
        player.score += 1;
        player.score
            = player.score > SCORE_LIMIT ? SCORE_LIMIT : player.score;
    } else {
        pbCtx.fillText("===== GAME  OVER =====", SCREEN_WIDTH / 2, 12);
        pbCtx.fillText("--- TAP TO RESTART ---", SCREEN_WIDTH / 2, 24);
    }
    pbCtx.fillText("SCORE: " + player.score, 2, 12);
}

function onPressed(n) {}

function onMove(n) {}

function onReleased(n) {}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Kagilum SAS.
 *
 * This file is part of iceScrum.
 *
 * iceScrum is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License.
 *
 * iceScrum is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iceScrum.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *
 * Vincent Barrier (vbarrier@kagilum.com)
 * Nicolas Noullet (nnoullet@kagilum.com)
 *
 */

//= encoding          UTF-8

//= require           vendors/jquery/jquery-3.1.1.min
//= require           vendors/jquery/jquery-migrate-1.4.1.min
//= require_full_tree vendors/jquery

//= require_full_tree vendors/vanilla

//= require           vendors/bootstrap/bootstrap.min
//= require           vendors/bootstrap/bootstrap.offcanvas.min
//= require           vendors/bootstrap/bootstrap-slider.min

//= require           vendors/angular/angular.min
//= require           vendors/angular/angular-route.min
//= require           vendors/angular/angular-resource.min

//= require           vendors/angular/plugins/ng-pdfviewer.min
//= require_full_tree vendors/i18n

//= require_full_tree vendors

//= require_full_tree application/plugins
//= require           application/isCore
//= require           application/isApplication
//= require           application/controllers
//= require           application/directives
//= require           application/filters
//= require           application/services
//= require_full_tree application

<commit_msg>fix pdf viewer call<commit_after>/*
 * Copyright (c) 2014 Kagilum SAS.
 *
 * This file is part of iceScrum.
 *
 * iceScrum is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License.
 *
 * iceScrum is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iceScrum.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *
 * Vincent Barrier (vbarrier@kagilum.com)
 * Nicolas Noullet (nnoullet@kagilum.com)
 *
 */

//= encoding          UTF-8

//= require           vendors/jquery/jquery-3.1.1.min
//= require           vendors/jquery/jquery-migrate-1.4.1.min
//= require_full_tree vendors/jquery

//= require_full_tree vendors/vanilla

//= require           vendors/bootstrap/bootstrap.min
//= require           vendors/bootstrap/bootstrap.offcanvas.min
//= require           vendors/bootstrap/bootstrap-slider.min

//= require           vendors/angular/angular.min
//= require           vendors/angular/angular-route.min
//= require           vendors/angular/angular-resource.min

//= require           vendors/angular/plugins/ng-pdfviewer
//= require_full_tree vendors/i18n

//= require_full_tree vendors

//= require_full_tree application/plugins
//= require           application/isCore
//= require           application/isApplication
//= require           application/controllers
//= require           application/directives
//= require           application/filters
//= require           application/services
//= require_full_tree application

<|endoftext|>


<commit_before>/*
 *  alt.musica repertoire, May 2019
 */

var mayRecordingRepertoire = {

	title: 'alt.musica Repertoire for recording, 19 May 2019',

	description: 'Backing tracks with winds, string quartet and rhythm section',

	selector: '#manifest-recording-may-2019',

	baseUrl: 'http://davidelainealt.com/scores/',

	programOrder: [ '01', '02', '04', '03' ],

	program: {
        '01': "Listening",
		'02': "Rising",
		'03': "Bechaini",
		'04': "It Only Happens Once"
	},

	bookOrder: [ ],
	books: { },
	partsInBooks: {
	},
	songs: {
	}
};

var partSelection = {
	"English Horn": {
        "It Only Happens Once": [ "English Horn in F" ],
	},
	"Bass Clarinet": {
        "It Only Happens Once": [ "Clarinet in A" ],
        "Listening": [ "Bass Clarinet in Bb for Bassoon" ],
		"Bechaini": [ "Bass Clarinet in Bb", "Voice" ],
        "Rising": [ "Bass Clarinet in Bb for Clarinet", "Bass Clarinet in Bb for Bassoon" ],
	}
	"Bass": {
        "It Only Happens Once": [ "Bass" ]
    }
};

var manifest = new Manifest(mayRecordingRepertoire);
manifest.addBooks(altMusicaBooks);
manifest.addSongsAndPartsInBooks(originalSongs);
manifest.addSongsAndPartsInBooks(arrangedSongs);
console.log('partSelection', partSelection);
manifest.selectPartsInBooks(partSelection);
var content = manifest.displayManifest() ;
manifest.placeManifestOnReady(content) ;

<commit_msg>fixing bug<commit_after>/*
 *  alt.musica repertoire, May 2019
 */

var mayRecordingRepertoire = {

	title: 'alt.musica Repertoire for recording, 19 May 2019',

	description: 'Backing tracks with winds, string quartet and rhythm section',

	selector: '#manifest-recording-may-2019',

	baseUrl: 'http://davidelainealt.com/scores/',

	programOrder: [ '01', '02', '04', '03' ],

	program: {
        '01': "Listening",
		'02': "Rising",
		'03': "Bechaini",
		'04': "It Only Happens Once"
	},

	bookOrder: [ ],
	books: { },
	partsInBooks: {
	},
	songs: {
	}
};

var partSelection = {
	"English Horn": {
        "It Only Happens Once": [ "English Horn in F" ],
	},
	"Bass Clarinet": {
        "It Only Happens Once": [ "Clarinet in A" ],
        "Listening": [ "Bass Clarinet in Bb for Bassoon" ],
		"Bechaini": [ "Bass Clarinet in Bb", "Voice" ],
        "Rising": [ "Bass Clarinet in Bb for Clarinet", "Bass Clarinet in Bb for Bassoon" ],
	},
	"Bass": {
        "It Only Happens Once": [ "Bass" ]
    }
};

var manifest = new Manifest(mayRecordingRepertoire);
manifest.addBooks(altMusicaBooks);
manifest.addSongsAndPartsInBooks(originalSongs);
manifest.addSongsAndPartsInBooks(arrangedSongs);
console.log('partSelection', partSelection);
manifest.selectPartsInBooks(partSelection);
var content = manifest.displayManifest() ;
manifest.placeManifestOnReady(content) ;

<|endoftext|>


<commit_before>$('<div>').append($(el).clone()).html();
<commit_msg>Just use outerHTML<commit_after>$(el).prop('outerHTML');
<|endoftext|>


<commit_before>'use strict';

let Resource = function($document, $resource, $cacheFactory, $window, npolarApiConfig) {
  'ngInject';
  // ['url', 'scheme', 'slash', 'host', 'port', 'path', 'query', 'hash']
  const PARSE_URL = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
  let appBase = PARSE_URL.exec($document[0].getElementsByTagName('base')[0].href)[5].replace(/^\//, '').replace(/\/$/, '');
  // Path to resource, relative to /base/ defined in index.html
  let href = function (id) {
    let hrefBase = this.uiBase.replace(appBase, '').replace(/^\/+/, '');
    return (hrefBase ? hrefBase + '/': '') + id;
  };

  let editHref = function(id) {
    return this.href(id) + '/edit';
  };

  let newHref = function() {
    return this.editHref('__new');
  };

  let _base = function(service) {
    // @todo canonicalUri?
    return (typeof service.base === "string") ? service.base : npolarApiConfig.base;
  };

  // Generate random UUID, from http://stackoverflow.com/a/8809472
  // jshint -W016, -W116
  this.randomUUID = function () {
    var d = new Date().getTime();
    if($window.performance && typeof $window.performance.now === "function"){
        d += performance.now(); //use high-precision timer if available
    }
    var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = (d + Math.random()*16)%16 | 0;
        d = Math.floor(d/16);
        return (c == 'x' ? r : (r&0x3|0x8)).toString(16);
    });
    return uuid;
  };

  // NpolarApiResource factory
  // @param service e.g. { path: '/dataset', 'resource': 'Dataset'}
  // @return NpolarApiResource - extended ngResource
  // @todo service.get == null|GET|JSONP
  // @todo make extending ngResource optional
  // @todo Support user-supplied extending
  // @todo Support non-search engine query/array/fetch
  this.resource = function(service) {

    let base = _base(service);
    let cache = service.cache;

    // Default parameters
    let params = {
      id: null,
      limit: 100,
      format: 'json',
      q: '',
      variant: 'atom'
    };

    //let fields_feed = (angular.isString(service.fields)) ? service.fields : null ;
    let fields_query = (typeof service.fields === 'string') ? service.fields : 'id,title,name,code,titles,links,created,updated';

    //let params_feed = angular.extend({}, params, { fields: fields_feed });
    let params_query = Object.assign({}, params, {
      variant: 'array',
      limit: 1000,
      fields: fields_query
    });

    const TIMEOUT = 20000;

    let resource = $resource(base + service.path + '/:id', {}, {
      feed: {
        method: 'GET',
        params: params,
        headers: {
          Accept: 'application/json, application/vnd.geo+json'
        },
        cache,
        timeout: TIMEOUT
      },
      query: {
        method: 'GET',
        params: params_query,
        isArray: true,
        cache,
        timeout: TIMEOUT
      },
      array: {
        method: 'GET',
        params: params_query,
        isArray: true,
        cache,
        timeout: TIMEOUT
      },
      facets: {
        method: 'GET',
        params: Object.assign({}, params, { limit: 0 }),
        isArray: true,
        cache,
        timeout: TIMEOUT,
        transformResponse(data) {
          if (!data) {
            return [];
          }
          return JSON.parse(data).feed.facets.map(facet => {
            let key = Object.keys(facet)[0];
            return {
              facet: key,
              terms: facet[key]
            };
          });
        }
      },
      fetch: {
        method: 'GET',
        params: {},
        headers: {
          Accept: 'application/json'
        },
        cache,
        timeout: TIMEOUT
      },
      remove: {
        cache,
        method: 'DELETE',
        timeout: TIMEOUT
      },
      delete: {
        cache,
        method: 'DELETE',
        timeout: TIMEOUT
      },
      update: {
        cache,
        method: 'PUT',
        params: {
          id: '@id'
        },
        headers: {
          Accept: 'application/json'
        },
        timeout: TIMEOUT
      }
    });

    resource.uiBase = service.uiBase || service.path;
    resource.path = base + service.path;
    resource.href = href;
    resource.newHref = newHref;
    resource.editHref = editHref;
    resource.randomUUID = this.randomUUID;

    return resource;

  };
};

module.exports = Resource;
<commit_msg>Allow no base<commit_after>'use strict';

let Resource = function($document, $resource, $cacheFactory, $window, npolarApiConfig) {
  'ngInject';
  // ['url', 'scheme', 'slash', 'host', 'port', 'path', 'query', 'hash']
  const PARSE_URL = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
  let base = $document[0].getElementsByTagName('base')[0];
  let appBase = base ? PARSE_URL.exec(base.href)[5].replace(/^\//, '').replace(/\/$/, '') : '';
  // Path to resource, relative to /base/ defined in index.html
  let href = function (id) {
    let hrefBase = this.uiBase.replace(appBase, '').replace(/^\/+/, '');
    return (hrefBase ? hrefBase + '/': '') + id;
  };

  let editHref = function(id) {
    return this.href(id) + '/edit';
  };

  let newHref = function() {
    return this.editHref('__new');
  };

  let _base = function(service) {
    // @todo canonicalUri?
    return (typeof service.base === "string") ? service.base : npolarApiConfig.base;
  };

  // Generate random UUID, from http://stackoverflow.com/a/8809472
  // jshint -W016, -W116
  this.randomUUID = function () {
    var d = new Date().getTime();
    if($window.performance && typeof $window.performance.now === "function"){
        d += performance.now(); //use high-precision timer if available
    }
    var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = (d + Math.random()*16)%16 | 0;
        d = Math.floor(d/16);
        return (c == 'x' ? r : (r&0x3|0x8)).toString(16);
    });
    return uuid;
  };

  // NpolarApiResource factory
  // @param service e.g. { path: '/dataset', 'resource': 'Dataset'}
  // @return NpolarApiResource - extended ngResource
  // @todo service.get == null|GET|JSONP
  // @todo make extending ngResource optional
  // @todo Support user-supplied extending
  // @todo Support non-search engine query/array/fetch
  this.resource = function(service) {

    let base = _base(service);
    let cache = service.cache;

    // Default parameters
    let params = {
      id: null,
      limit: 100,
      format: 'json',
      q: '',
      variant: 'atom'
    };

    //let fields_feed = (angular.isString(service.fields)) ? service.fields : null ;
    let fields_query = (typeof service.fields === 'string') ? service.fields : 'id,title,name,code,titles,links,created,updated';

    //let params_feed = angular.extend({}, params, { fields: fields_feed });
    let params_query = Object.assign({}, params, {
      variant: 'array',
      limit: 1000,
      fields: fields_query
    });

    const TIMEOUT = 20000;

    let resource = $resource(base + service.path + '/:id', {}, {
      feed: {
        method: 'GET',
        params: params,
        headers: {
          Accept: 'application/json, application/vnd.geo+json'
        },
        cache,
        timeout: TIMEOUT
      },
      query: {
        method: 'GET',
        params: params_query,
        isArray: true,
        cache,
        timeout: TIMEOUT
      },
      array: {
        method: 'GET',
        params: params_query,
        isArray: true,
        cache,
        timeout: TIMEOUT
      },
      facets: {
        method: 'GET',
        params: Object.assign({}, params, { limit: 0 }),
        isArray: true,
        cache,
        timeout: TIMEOUT,
        transformResponse(data) {
          if (!data) {
            return [];
          }
          return JSON.parse(data).feed.facets.map(facet => {
            let key = Object.keys(facet)[0];
            return {
              facet: key,
              terms: facet[key]
            };
          });
        }
      },
      fetch: {
        method: 'GET',
        params: {},
        headers: {
          Accept: 'application/json'
        },
        cache,
        timeout: TIMEOUT
      },
      remove: {
        cache,
        method: 'DELETE',
        timeout: TIMEOUT
      },
      delete: {
        cache,
        method: 'DELETE',
        timeout: TIMEOUT
      },
      update: {
        cache,
        method: 'PUT',
        params: {
          id: '@id'
        },
        headers: {
          Accept: 'application/json'
        },
        timeout: TIMEOUT
      }
    });

    resource.uiBase = service.uiBase || service.path;
    resource.path = base + service.path;
    resource.href = href;
    resource.newHref = newHref;
    resource.editHref = editHref;
    resource.randomUUID = this.randomUUID;

    return resource;

  };
};

module.exports = Resource;
<|endoftext|>


<commit_before>basePath = '../';

files = [
  JASMINE,
  JASMINE_ADAPTER,
  'app/lib/angular/angular.js',
  'app/lib/angular/angular-*.js',
  'test/lib/angular/angular-mocks.js',
  'app/js/**/*.js',
  'test/unit/**/*.js'
];

exclude = ['app/js/services/diffMatchPatchWorker.js'];

autoWatch = true;

// browsers = ['chromium-browser'];
browsers = ['PhantomJS'];

junitReporter = {
  outputFile: 'test_out/unit.xml',
  suite: 'unit'
};
<commit_msg>Update karma.conf.js<commit_after>module.exports = function (config) {
  config.set({
    basePath: '../',

    files: [
      'app/lib/angular/angular.js',
      'app/lib/angular/angular-*.js',
      'test/lib/angular/angular-mocks.js',
      'app/js/**/*.js',
      'test/unit/**/*.js'
    ],

    exclude: ['app/js/services/diffMatchPatchWorker.js'],

    autoWatch: true,

    // browsers: ['chromium-browser'],
    browsers: ['PhantomJS'],

    junitReporter: {
      outputFile: 'test_out/unit.xml',
      suite: 'unit'
    },

    frameworks: ['jasmine']
  });
};
<|endoftext|>


<commit_before>'use strict';

exports.scripts = {
  commit: {
    description: 'This uses commitizen to help us generate beautifully formatted commit messages',
    script: 'git-cz'
  },
  test: {
    description: 'Test, lint & generate coverage',
    default: 'p-s -p test.lint,test.mocha',
    lint: {
      script: 'eslint index.js test',
      description: 'Lint code'
    },
    mocha: {
      description: 'Run tests w/ Mocha',
      script: 'nyc mocha'
    }
  },
  coverage: {
    description: 'Generate coverage report & send to codecov.io',
    default: 'p-s coverage.report,coverage.send',
    report: {
      description: 'Generate LCOV report via nyc',
      script: 'nyc report -r lcov'
    },
    send: {
      description: 'Send coverage stats to codecov.io',
      script: 'codecov'
    }
  },
  release: {
    description: 'Release with semantic-release',
    script: 'semantic-release pre && npm publish && semantic-release post'
  },
  hooks: {
    'commitmsg': 'opt --out commit-msg --exec "validate-commit-msg"',
    'precommit': 'opt --out pre-commit --exec "npm test"'
  }
};
<commit_msg>chore(scripts): remove unused hooks scripts<commit_after>'use strict';

exports.scripts = {
  commit: {
    description: 'This uses commitizen to help us generate beautifully formatted commit messages',
    script: 'git-cz'
  },
  test: {
    description: 'Test, lint & generate coverage',
    default: 'p-s -p test.lint,test.mocha',
    lint: {
      script: 'eslint index.js test',
      description: 'Lint code'
    },
    mocha: {
      description: 'Run tests w/ Mocha',
      script: 'nyc mocha'
    }
  },
  coverage: {
    description: 'Generate coverage report & send to codecov.io',
    default: 'p-s coverage.report,coverage.send',
    report: {
      description: 'Generate LCOV report via nyc',
      script: 'nyc report -r lcov'
    },
    send: {
      description: 'Send coverage stats to codecov.io',
      script: 'codecov'
    }
  },
  release: {
    description: 'Release with semantic-release',
    script: 'semantic-release pre && npm publish && semantic-release post'
  }
};
<|endoftext|>