<commit_before>function GloballyPinnedTabs(tabs) {
    this.tabs = tabs;
    this.activeTab = tabs.filter(function(x) { return x.realTab && x.realTab.active; })[0];
}

GloballyPinnedTabs.create = function(serializedTabs) {
    serializedTabs = serializedTabs || [{
        startupUrl: 'https://www.toggl.com/app/timer'
    }, {
        startupUrl: 'https://www.beeminder.com/danielhilgarth'
    }];

    var result = new GloballyPinnedTabs(serializedTabs.map(function(x) {
        return new GloballyPinnedTab(x.startupUrl, x.currentUrl, x.favIconUrl, x.title);
    }));

    var onCloseCallback = function(pinnedTab) {
        result.onPinnedTabClosed(pinnedTab);
    };
    for (var i = 0; i < result.tabs.length; i++)
        result.tabs[i].onCloseCallback = onCloseCallback;

    return result;
};

GloballyPinnedTabs.prototype = {
    serialize: function() {
        return this.tabs.map(GloballyPinnedTab.serialize);
    },

    addTab: function(tab) {
        var self = this;
        var newTab = new GloballyPinnedTab(tab.url, tab.url, tab.favIconUrl, tab.title, function(pinnedTab) {
            self.onPinnedTabClosed(pinnedTab);
        });
        this.tabs.push(newTab);
        Storage.persistData();
        Chrome.getAllWindows(function(windows) { newTab.createTabs(windows); });
    },

    createTabs: function(windows) {
        for (var i = 0; i < this.tabs.length; i++)
            this.tabs[i].createTabs(windows);
    },

    createTabsForWindow: function(window) {
        for (var i = 0; i < this.tabs.length; i++)
            this.tabs[i].createTabForWindow(window);
    },

    activateWindow: function(window, callback) {
        if(this.tabs.length === 0) {
            callback();
            return;
        }

        var oldWindowId;
        if(this.tabs[0].realTab)
            oldWindowId = this.tabs[0].realTab.windowId;

        if(oldWindowId === window.id) {
            callback();
            return;
        }

        if(oldWindowId !== undefined)
            this.moveTabs(window, oldWindowId, callback);
        else
            this.recreateTabs(window, callback);
    },

    recreateTabs: function(targetWindow, callback) {
        var dummyTabs = this.getDummyTabsForWindow(targetWindow, false);
        var dummyTabIds = dummyTabs.map(function(x) { return x.id; });
        chrome.tabs.remove(dummyTabIds);
        for (var i = 0; i < this.tabs.length; i++)
            this.tabs[i].createRealTab(targetWindow, callback, true);
    },

    getDummyTabsForWindow: function(window) {
        var result = {
            foundTabs: [],
            missingTabs: []
        };
        for (var i = 0; i < this.tabs.length; i++) {
            var tab = this.tabs[i];
            var dummyTab = tab.dummyTabs[window.id];
            if(!dummyTab)
                result.missingTabs.push(tab);
            else
                result.foundTabs.push(dummyTab);
        }

        return result;
    },

    moveTabs: function(targetWindow, sourceWindowId, callback) {

        var dummyTabs = this.getDummyTabsForWindow(targetWindow, true);
        var dummyTabIds = [];
        var smallestDummyTabsIndex = 10000;
        var i;
        var tab;
        var indexOfActiveTab = -1;
        for (i = 0; i < dummyTabs.foundTabs.length; i++) {
            tab = dummyTabs.foundTabs[i];
            dummyTabIds.push(tab.id);
            smallestDummyTabsIndex = Math.min(smallestDummyTabsIndex, tab.index);
        }

        var realTabIds = [];
        var smallestRealTabsIndex = 10000;
        for (i = 0; i < this.tabs.length; i++) {
            tab = this.tabs[i];
            realTabIds.push(tab.realTab.id);
            smallestRealTabsIndex = Math.min(smallestRealTabsIndex, tab.realTab.index);
        }

        var finished = 0;
        var total = realTabIds.length + dummyTabs.foundTabs.length + dummyTabs.missingTabs.length;
        var self = this;

        var callCallback = function() {
            finished++;
            if(finished === total)
                callback();
        };

        Chrome.moveTabs(realTabIds, targetWindow.id, smallestRealTabsIndex, function(tabs) {
            for (var i = 0; i < tabs.length; ++i) {
                self.tabs[i].realTab = tabs[i];
                Chrome.pinTab(tabs[i].id, callCallback);
            }
        });

        if(dummyTabIds.length) {
            Chrome.moveTabs(dummyTabIds, sourceWindowId, smallestDummyTabsIndex, function (tabs) {
                for (var i = 0; i < tabs.length; ++i) {
                    delete self.tabs[i].dummyTabs[targetWindow.id];
                    self.tabs[i].dummyTabs[sourceWindowId] = tabs[i];
                    Chrome.pinTab(tabs[i].id, callCallback);
                }
            });
        }

        if(dummyTabs.missingTabs.length) {
            Chrome.getWindow(sourceWindowId, function (window) {
                for (i = 0; i < dummyTabs.missingTabs.length; i++) {
                    dummyTabs.missingTabs[i].createTabForWindow(window, callCallback);
                }
            });
        }
    },

    updateTab: function(tab) {
        var pinnedTab = this.getTabForIdOfRealTab(tab.id);
        if(pinnedTab) {
            pinnedTab.updateRealTab(tab);
            Storage.persistData();
        } else {
            pinnedTab = this.getTabForAnyTabId(tab.id);
            if(pinnedTab)
                pinnedTab.updateDummyTab(pinnedTab);
        }
    },

    getTabForIdOfRealTab: function(realTabId) {
        return this.tabs.filter(function(x) { return x.realTab && (x.realTab.id === realTabId); })[0];
    },

    getTabForAnyTabId: function(tabId) {
        return this.tabs.filter(function(x) {
            if (x.realTab && (x.realTab.id === tabId))
                return true;
            for (var key in x.dummyTabs) {
                if (x.dummyTabs[key] === tabId) {
                    return true;
                }
            }
        })[0];
    },

    handleClosedTab: function(tabId) {
        var tab = this.getTabForAnyTabId(tabId);
        if (tab) {
            if (tab.realTab && (tab.realTab.id === tabId))
                tab.realTab = undefined;
            else {
                for (var key in tab.dummyTabs) {
                    if (tab.dummyTabs[key].id === tabId) {
                        delete tab.dummyTabs[key];
                        break;
                    }
                }
            }
            tab.close();
        }
    },

    handleClosedWindow: function(windowId) {
        for (var i = 0; i < this.tabs.length; i++)
            this.tabs[i].handleClosedWindow(windowId);
    },

    onPinnedTabClosed: function(pinnedTab) {
        this.tabs.remove(pinnedTab);
        console.log(this.tabs);
        Storage.persistData();
    }
};<commit_msg>Reformat<commit_after>function GloballyPinnedTabs(tabs) {
    this.tabs = tabs;
    this.activeTab = tabs.filter(function(x) { return x.realTab && x.realTab.active; })[0];
}

GloballyPinnedTabs.create = function(serializedTabs) {
    serializedTabs = serializedTabs || [{
        startupUrl: 'https://www.toggl.com/app/timer'
    }, {
        startupUrl: 'https://www.beeminder.com/danielhilgarth'
    }];

    var result = new GloballyPinnedTabs(serializedTabs.map(function(x) {
        return new GloballyPinnedTab(x.startupUrl, x.currentUrl, x.favIconUrl, x.title);
    }));

    var onCloseCallback = function(pinnedTab) {
        result.onPinnedTabClosed(pinnedTab);
    };
    for (var i = 0; i < result.tabs.length; i++)
        result.tabs[i].onCloseCallback = onCloseCallback;

    return result;
};

GloballyPinnedTabs.prototype = {
    serialize: function() {
        return this.tabs.map(GloballyPinnedTab.serialize);
    },

    addTab: function(tab) {
        var self = this;
        var newTab = new GloballyPinnedTab(tab.url, tab.url, tab.favIconUrl, tab.title, function(pinnedTab) {
            self.onPinnedTabClosed(pinnedTab);
        });
        this.tabs.push(newTab);
        Storage.persistData();
        Chrome.getAllWindows(function(windows) { newTab.createTabs(windows); });
    },

    createTabs: function(windows) {
        for (var i = 0; i < this.tabs.length; i++)
            this.tabs[i].createTabs(windows);
    },

    createTabsForWindow: function(window) {
        for (var i = 0; i < this.tabs.length; i++)
            this.tabs[i].createTabForWindow(window);
    },

    activateWindow: function(window, callback) {
        if(this.tabs.length === 0) {
            callback();
            return;
        }

        var oldWindowId;
        if(this.tabs[0].realTab)
            oldWindowId = this.tabs[0].realTab.windowId;

        if(oldWindowId === window.id) {
            callback();
            return;
        }

        if(oldWindowId !== undefined)
            this.moveTabs(window, oldWindowId, callback);
        else
            this.recreateTabs(window, callback);
    },

    recreateTabs: function(targetWindow, callback) {
        var dummyTabs = this.getDummyTabsForWindow(targetWindow, false);
        var dummyTabIds = dummyTabs.map(function(x) { return x.id; });
        chrome.tabs.remove(dummyTabIds);
        for (var i = 0; i < this.tabs.length; i++)
            this.tabs[i].createRealTab(targetWindow, callback, true);
    },

    getDummyTabsForWindow: function(window) {
        var result = {
            foundTabs: [],
            missingTabs: []
        };
        for (var i = 0; i < this.tabs.length; i++) {
            var tab = this.tabs[i];
            var dummyTab = tab.dummyTabs[window.id];
            if(!dummyTab)
                result.missingTabs.push(tab);
            else
                result.foundTabs.push(dummyTab);
        }

        return result;
    },

    moveTabs: function(targetWindow, sourceWindowId, callback) {

        var dummyTabs = this.getDummyTabsForWindow(targetWindow, true);
        var dummyTabIds = [];
        var smallestDummyTabsIndex = 10000;
        var i;
        var tab;
        var indexOfActiveTab = -1;
        for (i = 0; i < dummyTabs.foundTabs.length; i++) {
            tab = dummyTabs.foundTabs[i];
            dummyTabIds.push(tab.id);
            smallestDummyTabsIndex = Math.min(smallestDummyTabsIndex, tab.index);
        }

        var realTabIds = [];
        var smallestRealTabsIndex = 10000;
        for (i = 0; i < this.tabs.length; i++) {
            tab = this.tabs[i];
            realTabIds.push(tab.realTab.id);
            smallestRealTabsIndex = Math.min(smallestRealTabsIndex, tab.realTab.index);
        }

        var finished = 0;
        var total = realTabIds.length + dummyTabs.foundTabs.length + dummyTabs.missingTabs.length;
        var self = this;

        var callCallback = function() {
            finished++;
            if(finished === total)
                callback();
        };

        Chrome.moveTabs(realTabIds, targetWindow.id, smallestRealTabsIndex, function(tabs) {
            for (var i = 0; i < tabs.length; ++i) {
                self.tabs[i].realTab = tabs[i];
                Chrome.pinTab(tabs[i].id, callCallback);
            }
        });

        if(dummyTabIds.length) {
            Chrome.moveTabs(dummyTabIds, sourceWindowId, smallestDummyTabsIndex, function (tabs) {
                for (var i = 0; i < tabs.length; ++i) {
                    delete self.tabs[i].dummyTabs[targetWindow.id];
                    self.tabs[i].dummyTabs[sourceWindowId] = tabs[i];
                    Chrome.pinTab(tabs[i].id, callCallback);
                }
            });
        }

        if(dummyTabs.missingTabs.length) {
            Chrome.getWindow(sourceWindowId, function (window) {
                for (i = 0; i < dummyTabs.missingTabs.length; i++) {
                    dummyTabs.missingTabs[i].createTabForWindow(window, callCallback);
                }
            });
        }
    },

    updateTab: function(tab) {
        var pinnedTab = this.getTabForIdOfRealTab(tab.id);
        if(pinnedTab) {
            pinnedTab.updateRealTab(tab);
            Storage.persistData();
        } else {
            pinnedTab = this.getTabForAnyTabId(tab.id);
            if(pinnedTab)
                pinnedTab.updateDummyTab(pinnedTab);
        }
    },

    getTabForIdOfRealTab: function(realTabId) {
        return this.tabs.filter(function(x) { return x.realTab && (x.realTab.id === realTabId); })[0];
    },

    getTabForAnyTabId: function(tabId) {
        return this.tabs.filter(function(x) {
            if (x.realTab && (x.realTab.id === tabId))
                return true;
            for (var key in x.dummyTabs) {
                if (x.dummyTabs[key] === tabId) {
                    return true;
                }
            }
        })[0];
    },

    handleClosedTab: function(tabId) {
        var tab = this.getTabForAnyTabId(tabId);
        if (tab) {
            if (tab.realTab && (tab.realTab.id === tabId))
                tab.realTab = undefined;
            else {
                for (var key in tab.dummyTabs) {
                    if (tab.dummyTabs[key].id === tabId) {
                        delete tab.dummyTabs[key];
                        break;
                    }
                }
            }
            tab.close();
        }
    },

    handleClosedWindow: function(windowId) {
        for (var i = 0; i < this.tabs.length; i++)
            this.tabs[i].handleClosedWindow(windowId);
    },

    onPinnedTabClosed: function(pinnedTab) {
        this.tabs.remove(pinnedTab);
        console.log(this.tabs);
        Storage.persistData();
    }
};<|endoftext|>


<commit_before>var htmlparser = require('./vendor/htmlparser2')
var entities = require('./vendor/entities')
var React = require('react-native')
var {
  LinkingIOS,
  StyleSheet,
  Text,
  View,
} = React

var LINE_BREAK = '\n'
var PARAGRAPH_BREAK = ''
var BULLET = '\u2022 '
var CIRCLEBULLET = '\u29BF '
var WHITEBULLET = '\u25E6 '
var TRIANGLEBULLET = '\u2023 '

function htmlToElement(rawHtml, opts, done) {
  function domToElement(dom, parent) {
    if (!dom) return null

    return dom.map((node, index, list) => {
      if (opts.customRenderer) {
        var rendered = opts.customRenderer(node, index, list)
        if (rendered || rendered === null) return rendered
      }

      if (node.type == 'text') {
        if(node.data == "\n")
          return (<Text key={index} style={{fontSize: 8}}>{node.data}</Text>);

        return (
          <Text key={index} style={parent ? opts.styles[parent.name] : null}>
            {entities.decodeHTML(node.data)}
          </Text>
        )
      }

      if (node.type == 'tag') {
        var linkPressHandler = null
        if (node.name == 'a' && node.attribs && node.attribs.href) {
          linkPressHandler = () => opts.linkHandler(entities.decodeHTML(node.attribs.href))
        }

        var preListString = "";
        if(node.name == 'li') {
          //go back up tree to find how many 'ul' tags there are
          //where number of UL tags = indentation level

          var tempNode = node;
          var numIndentationLevels = 0;
          while(tempNode.parent != null) {
            if(tempNode.parent.name == 'ul') {
              numIndentationLevels = numIndentationLevels + 1;
              preListString = "    " + preListString;
            }

            tempNode = tempNode.parent;
          }

          switch(numIndentationLevels) {
            case 4:
              preListString = preListString + TRIANGLEBULLET;
              break;
            case 3:
              preListString = preListString + CIRCLEBULLET;
              break;
            case 2:
              preListString = preListString + WHITEBULLET;
              break;
            default:
              preListString = preListString + BULLET;
          }
        }

        var postParagraphString = null;
        // if(node.name == 'p' && index < list.length-1) {
        //   postParagraphString = PARAGRAPH_BREAK;
        // }
        if(node.name == 'p') {
          postParagraphString = PARAGRAPH_BREAK;
        }
        // if(node.name == 'p') {

        //   var tempNode = node;
        //   while(tempNode.next != null) {
        //     tempNode = tempNode.next;
        //     console.log(tempNode.type);
        //     if(tempNode.type != "text")
        //       break;
        //   }
        //   if(tempNode.name == 'ul')
        //     postParagraphString = null;
        // }

        return (
          <Text key={index}
                onPress={linkPressHandler}
                style={{backgroundColor: '#ffe5e5'}} >
            {node.name == 'pre' ? LINE_BREAK : null}
            {node.name == 'li' ? preListString : null}
            {domToElement(node.children, node)}
            {node.name == 'br' ? LINE_BREAK : null}
            {node.name == 'hr' ? "horizontal-rule not implemented yet\n" : null}
            {postParagraphString}
            {node.name == 'h1' ||
             node.name == 'h2' ||
             node.name == 'h3' ||
             node.name == 'h4' ||
             node.name == 'h5' ? PARAGRAPH_BREAK : null}
           </Text>
        )
      }
    })
  }

  var handler = new htmlparser.DomHandler(function (err, dom) {
    if (err) done(err)
    done(null, domToElement(dom))
  })
  var parser = new htmlparser.Parser(handler)
  parser.write(rawHtml)
  parser.done()
}

var HTMLView = React.createClass({
  mixins: [
    React.addons.PureRenderMixin,
  ],
  getDefaultProps() {
    return {
      onLinkPress: LinkingIOS.openURL,
    }
  },
  getInitialState() {
    return {
      element: null,
    }
  },
  componentWillReceiveProps() {
    if (this.state.element) return
    this.startHtmlRender()
  },
  componentDidMount() {
    this.startHtmlRender()
  },
  startHtmlRender() {
    if (!this.props.value) return
    if (this.renderingHtml) return

    var opts = {
      linkHandler: this.props.onLinkPress,
      styles: Object.assign({}, baseStyles, this.props.stylesheet),
      customRenderer: this.props.renderNode,
    }

    this.renderingHtml = true
    htmlToElement(this.props.value, opts, (err, element) => {
      this.renderingHtml = false

      if (err) return (this.props.onError || console.error)(err)

      if (this.isMounted()) this.setState({element})
    })
  },
  render() {
    if (this.state.element) {
      return <View children={this.state.element} />
    }
    return <View />
  }
})

var boldStyle = {fontWeight: '500'}
var italicStyle = {fontStyle: 'italic'}
var codeStyle = {fontFamily: 'Menlo'}

var baseStyles = StyleSheet.create({
  b: boldStyle,
  strong: boldStyle,
  i: italicStyle,
  em: italicStyle,
  pre: codeStyle,
  code: codeStyle,
  a: {
    fontWeight: '500',
    color: '#007AFF',
  },
})

module.exports = HTMLView
<commit_msg>"fixed" the paragraph extra newlines<commit_after>var htmlparser = require('./vendor/htmlparser2')
var entities = require('./vendor/entities')
var React = require('react-native')
var {
  LinkingIOS,
  StyleSheet,
  Text,
  View,
} = React

var LINE_BREAK = '\n'
var PARAGRAPH_BREAK = ''
var BULLET = '\u2022 '
var CIRCLEBULLET = '\u29BF '
var WHITEBULLET = '\u25E6 '
var TRIANGLEBULLET = '\u2023 '

function htmlToElement(rawHtml, opts, done) {
  function domToElement(dom, parent) {
    if (!dom) return null

    return dom.map((node, index, list) => {
      if (opts.customRenderer) {
        var rendered = opts.customRenderer(node, index, list)
        if (rendered || rendered === null) return rendered
      }

      if (node.type == 'text') {
        if(node.data == "\n")
          return (<Text key={index} style={{fontSize: 8}}>{node.data}</Text>);

        return (
          <Text key={index} style={parent ? opts.styles[parent.name] : null}>
            {entities.decodeHTML(node.data)}
          </Text>
        )
      }

      if (node.type == 'tag') {
        var linkPressHandler = null
        if (node.name == 'a' && node.attribs && node.attribs.href) {
          linkPressHandler = () => opts.linkHandler(entities.decodeHTML(node.attribs.href))
        }

        var preListString = "";
        if(node.name == 'li') {
          //go back up tree to find how many 'ul' tags there are
          //where number of UL tags = indentation level

          var tempNode = node;
          var numIndentationLevels = 0;
          while(tempNode.parent != null) {
            if(tempNode.parent.name == 'ul') {
              numIndentationLevels = numIndentationLevels + 1;
              preListString = "    " + preListString;
            }

            tempNode = tempNode.parent;
          }

          switch(numIndentationLevels) {
            case 4:
              preListString = preListString + TRIANGLEBULLET;
              break;
            case 3:
              preListString = preListString + CIRCLEBULLET;
              break;
            case 2:
              preListString = preListString + WHITEBULLET;
              break;
            default:
              preListString = preListString + BULLET;
          }
        }

        if(node.name == 'p') {
        }

        return (
          <Text key={index}
                onPress={linkPressHandler}
                style={{backgroundColor: '#ffe5e5'}} >
            {node.name == 'pre' ? LINE_BREAK : null}
            {node.name == 'li' ? preListString : null}
            {domToElement(node.children, node)}
            {node.name == 'br' ? LINE_BREAK : null}
            {node.name == 'hr' ? "horizontal-rule not implemented yet\n" : null}
           </Text>
        )
      }
    })
  }

  var handler = new htmlparser.DomHandler(function (err, dom) {
    if (err) done(err)
    done(null, domToElement(dom))
  })
  var parser = new htmlparser.Parser(handler)
  parser.write(rawHtml)
  parser.done()
}

var HTMLView = React.createClass({
  mixins: [
    React.addons.PureRenderMixin,
  ],
  getDefaultProps() {
    return {
      onLinkPress: LinkingIOS.openURL,
    }
  },
  getInitialState() {
    return {
      element: null,
    }
  },
  componentWillReceiveProps() {
    if (this.state.element) return
    this.startHtmlRender()
  },
  componentDidMount() {
    this.startHtmlRender()
  },
  startHtmlRender() {
    if (!this.props.value) return
    if (this.renderingHtml) return

    var opts = {
      linkHandler: this.props.onLinkPress,
      styles: Object.assign({}, baseStyles, this.props.stylesheet),
      customRenderer: this.props.renderNode,
    }

    this.renderingHtml = true
    htmlToElement(this.props.value, opts, (err, element) => {
      this.renderingHtml = false

      if (err) return (this.props.onError || console.error)(err)

      if (this.isMounted()) this.setState({element})
    })
  },
  render() {
    if (this.state.element) {
      return <View children={this.state.element} />
    }
    return <View />
  }
})

var boldStyle = {fontWeight: '500'}
var italicStyle = {fontStyle: 'italic'}
var codeStyle = {fontFamily: 'Menlo'}

var baseStyles = StyleSheet.create({
  b: boldStyle,
  strong: boldStyle,
  i: italicStyle,
  em: italicStyle,
  pre: codeStyle,
  code: codeStyle,
  a: {
    fontWeight: '500',
    color: '#007AFF',
  },
})

module.exports = HTMLView
<|endoftext|>


<commit_before>module.exports = {
  configureWebpack: {
    // Configuration applied to all builds
  },
  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true,
      mainProcessWatch: [],
      mainProcessArgs: [],
      builderOptions: {
        appId: "com.novocurrency.app",
        productName: "NovoCurrency Wallet",
        extraFiles: []
      }
    },
    i18n: {
      locale: "en",
      fallbackLocale: "en",
      localeDir: "locales",
      enableInSFC: false
    }
  },
  css: {
    loaderOptions: {
      less: {
        javascriptEnabled: true
      }
    }
  }
};
<commit_msg>remove productName for now<commit_after>module.exports = {
  configureWebpack: {
    // Configuration applied to all builds
  },
  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true,
      mainProcessWatch: [],
      mainProcessArgs: [],
      builderOptions: {
        appId: "com.novocurrency.app",
        extraFiles: []
      }
    },
    i18n: {
      locale: "en",
      fallbackLocale: "en",
      localeDir: "locales",
      enableInSFC: false
    }
  },
  css: {
    loaderOptions: {
      less: {
        javascriptEnabled: true
      }
    }
  }
};
<|endoftext|>


<commit_before>const Dotenv = require('dotenv-webpack');
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const webpack = require('webpack');
const { UnusedFilesWebpackPlugin } = require('unused-files-webpack-plugin');
const webpackConfig = {
  node: {
    process: true
  },
  devServer: {
    https: true,
    host: 'localhost',
    hotOnly: true,
    port: 8080
  },
  plugins: [
    new Dotenv({
      systemvars: true,
      silent: true
    }),
    new webpack.NormalModuleReplacementPlugin(/^any-promise$/, 'bluebird'),
    new ImageminPlugin({
      test: /\.(jpe?g|png|gif|svg)$/i,
      disable: process.env.NODE_ENV !== 'production',
      pngquant: {
        quality: '95-100'
      }
    })
  ]
};
if (process.env.NODE_ENV === 'production') {
  webpackConfig.plugins.push(new UnusedFilesWebpackPlugin({
      patterns: ['src/**/*.*'],
      failOnUnused: true,
      globOptions: {
        ignore: [
          'src/assets/images/flags/countries.json',
          'src/assets/images/networks/esn.svg',
          'src/translations/README.md',
          'src/translations/terms-of-conditions/en_US.json',
          'src/wallets/hybrid/MEWconnect/deterministicWalletPaths.js',
          'src/wallets/hardware/deterministicWalletPaths.js'
        ]
      }
    }));
}
module.exports = {
  baseUrl: './',
  configureWebpack: webpackConfig,
  chainWebpack: config => {
    config.module
      .rule('eslint')
      .use('eslint-loader')
      .options({ fix: true });
  }
};
<commit_msg>correct reason for ci build failure<commit_after>const Dotenv = require('dotenv-webpack');
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const webpack = require('webpack');
const { UnusedFilesWebpackPlugin } = require('unused-files-webpack-plugin');
const webpackConfig = {
  node: {
    process: true
  },
  devServer: {
    https: true,
    host: 'localhost',
    hotOnly: true,
    port: 8080
  },
  plugins: [
    new Dotenv({
      systemvars: true,
      silent: true
    }),
    new webpack.NormalModuleReplacementPlugin(/^any-promise$/, 'bluebird'),
    new ImageminPlugin({
      test: /\.(jpe?g|png|gif|svg)$/i,
      disable: process.env.NODE_ENV !== 'production',
      pngquant: {
        quality: '95-100'
      }
    })
  ]
};
if (process.env.NODE_ENV === 'production') {
  webpackConfig.plugins.push(new UnusedFilesWebpackPlugin({
      patterns: ['src/**/*.*'],
      failOnUnused: true,
      globOptions: {
        ignore: [
          'src/assets/images/flags/countries.json',
          'src/assets/images/networks/esn.svg',
          'src/translations/README.md',
          'src/translations/terms-of-conditions/en_US.json',
          'src/wallets/hybrid/MEWconnect/deterministicWalletPaths.js',
          'src/wallets/hardware/deterministicWalletPaths.js',
          // UnusedFilesWebpackPlugin marked the below files as unused
          'src/components/DropDownAddressSelector/DropDownAddressSelector.scss',
          'src/components/DropDownAddressSelector/DropDownAddressSelector.vue',
          'src/components/DropDownAddressSelector/index.js',
          'src/components/Transactions/TransactionEntry/index.js',
          'src/components/Transactions/TransactionEntry/TransactionEntry.scss',
          'src/dapps/RegisterDomain/components/FinalizeModal/FinalizeModal.scss',
          'src/dapps/RegisterDomain/components/FinalizeModal/FinalizeModal.vue',
          'src/dapps/RegisterDomain/containers/ManageENSContainer/ManageENSContainer.scss',
          'src/dapps/RegisterDomain/containers/ManageENSContainer/ManageENSContainer.vue',
          'src/layouts/InterfaceLayout/components/ProvidersRadioSelector/index.js',
          'src/layouts/InterfaceLayout/components/ProvidersRadioSelector/ProvidersRadioSelector.scss',
          'src/layouts/InterfaceLayout/components/ProvidersRadioSelector/ProvidersRadioSelector.vue'
        ]
      }
    }));
}
module.exports = {
  baseUrl: './',
  configureWebpack: webpackConfig,
  chainWebpack: config => {
    config.module
      .rule('eslint')
      .use('eslint-loader')
      .options({ fix: true });
  }
};
<|endoftext|>


<commit_before>const { getCompilationQuarterValue, getInfoFromPath } = require('../../deploy-helper')

let yamljs  = require("js-yaml"),
    glob    = require("glob"),
    axios   = require("axios"),
    moment  = require("moment"),
    fs      = require("fs-extra");

const DATE_FORMAT = "DD/MM/YYYY"

let dailyAudio = async function (lang, title, template, srcFunc, priorCheck) {
    let targetQuarterlies = glob.sync(`src/${lang}/${getCompilationQuarterValue(null, true)}/`);

    for (let targetQuarter of targetQuarterlies) {
        let quarterlyInfo = getInfoFromPath(targetQuarter)
        let process = async function (date) {
            try {
                let targetDate = date || moment()
                let infoSource = yamljs.load(fs.readFileSync(`${targetQuarter}/info.yml`), 'utf-8')
                let startDate = moment(infoSource.start_date, DATE_FORMAT)
                let endDate = moment(infoSource.end_date, DATE_FORMAT)

                if (!targetDate.isBetween(startDate, endDate)) {
                    return;
                }

                console.log(`Checking ${title} for ${targetDate.format(DATE_FORMAT)}`)

                let audioSource = yamljs.load(fs.readFileSync(`${targetQuarter}/audio.yml`), 'utf-8')

                let diff = targetDate.diff(startDate, 'days') + 7
                let week = Math.floor(diff / 7)
                let day = (diff % 7) + 1

                let audio = audioSource.audio.find(e => e.artist === template.artist)

                if (!audio) {
                    console.log(`Looks like a brand new ${title} audio section`)
                    audio = JSON.parse(JSON.stringify(template))
                    audioSource.audio.push(audio)
                }

                let track = audio.tracks.find(e => e.target === `${quarterlyInfo.language}/${quarterlyInfo.quarterly}/${String(week).padStart(2, '0')}/${String(day).padStart(2, '0')}`)

                if (track) {
                    console.log(`${title} for ${targetDate.format(DATE_FORMAT)} already exists.`)
                    return
                }

                let src = srcFunc(targetDate, week, day)
                let response = await axios.head(src);
                if (response.status === 200 && response.headers['content-type'].indexOf('audio/') >= 0) {
                    console.log(`Found ${title} for ${targetDate.format(DATE_FORMAT)}. Will commit`)
                    audio.tracks.push({
                        src: src,
                        target: `${quarterlyInfo.language}/${quarterlyInfo.quarterly}/${String(week).padStart(2, '0')}/${String(day).padStart(2, '0')}`
                    })
                    fs.outputFileSync(`${targetQuarter}/audio.yml`,
                        yamljs.dump(audioSource, {
                            lineWidth: -1
                        }).replace(/^(?!$)/mg, '  ').replace(/^/, '---\n')
                    );
                }
            } catch (e) {
                if (e && e.response && e.response.status === 404) {
                    console.log(`${title} file is not found`)
                }
            }
        }

        for (let i = priorCheck || 0; i >= 0; i--) {
            await process(moment().add(-1*i, 'd'))
        }
    }
}

let ellenWhiteAudio = async function () {
    await dailyAudio(
        "en",
        "EGW notes",
        {
            artist: "Ellen G. White Notes",
            image: "misc/ellen-white-notes.png",
            imageRatio: "square",
            tracks: []
        },
        function (targetDate, week, day) {
            return `https://egwhiteaudio.com/wp-content/uploads/${targetDate.format("YYYY/MM/YYYY-MM-DD")}.mp3`
        },
        1
    )
}

let indonesiaAudio = async function () {
    await dailyAudio(
        "in",
        "AWR Asia",
        {
            artist: "AWR Asia",
            tracks: []
        },
        function (targetDate, week, day) {
            let mapping = [7, 1, 2, 3, 4, 5, 6]
            return `https://podcasts.awr.org/Audio/Asia/LowResWeb/INDJA/SSL/INDJAaSSLx_${targetDate.format("YYYYMMDD")}_${mapping[day-1]}.mp3`
        }
    )
}

let run = async function () {
    await ellenWhiteAudio();
    await indonesiaAudio();
}

run().then(() => {
    return true
});<commit_msg>Increase priorCheck for EGW audio notes<commit_after>const { getCompilationQuarterValue, getInfoFromPath } = require('../../deploy-helper')

let yamljs  = require("js-yaml"),
    glob    = require("glob"),
    axios   = require("axios"),
    moment  = require("moment"),
    fs      = require("fs-extra");

const DATE_FORMAT = "DD/MM/YYYY"

let dailyAudio = async function (lang, title, template, srcFunc, priorCheck) {
    let targetQuarterlies = glob.sync(`src/${lang}/${getCompilationQuarterValue(null, true)}/`);

    for (let targetQuarter of targetQuarterlies) {
        let quarterlyInfo = getInfoFromPath(targetQuarter)
        let process = async function (date) {
            try {
                let targetDate = date || moment()
                let infoSource = yamljs.load(fs.readFileSync(`${targetQuarter}/info.yml`), 'utf-8')
                let startDate = moment(infoSource.start_date, DATE_FORMAT)
                let endDate = moment(infoSource.end_date, DATE_FORMAT)

                if (!targetDate.isBetween(startDate, endDate)) {
                    return;
                }

                console.log(`Checking ${title} for ${targetDate.format(DATE_FORMAT)}`)

                let audioSource = yamljs.load(fs.readFileSync(`${targetQuarter}/audio.yml`), 'utf-8')

                let diff = targetDate.diff(startDate, 'days') + 7
                let week = Math.floor(diff / 7)
                let day = (diff % 7) + 1

                let audio = audioSource.audio.find(e => e.artist === template.artist)

                if (!audio) {
                    console.log(`Looks like a brand new ${title} audio section`)
                    audio = JSON.parse(JSON.stringify(template))
                    audioSource.audio.push(audio)
                }

                let track = audio.tracks.find(e => e.target === `${quarterlyInfo.language}/${quarterlyInfo.quarterly}/${String(week).padStart(2, '0')}/${String(day).padStart(2, '0')}`)

                if (track) {
                    console.log(`${title} for ${targetDate.format(DATE_FORMAT)} already exists.`)
                    return
                }

                let src = srcFunc(targetDate, week, day)
                let response = await axios.head(src);
                if (response.status === 200 && response.headers['content-type'].indexOf('audio/') >= 0) {
                    console.log(`Found ${title} for ${targetDate.format(DATE_FORMAT)}. Will commit`)
                    audio.tracks.push({
                        src: src,
                        target: `${quarterlyInfo.language}/${quarterlyInfo.quarterly}/${String(week).padStart(2, '0')}/${String(day).padStart(2, '0')}`
                    })
                    fs.outputFileSync(`${targetQuarter}/audio.yml`,
                        yamljs.dump(audioSource, {
                            lineWidth: -1
                        }).replace(/^(?!$)/mg, '  ').replace(/^/, '---\n')
                    );
                }
            } catch (e) {
                if (e && e.response && e.response.status === 404) {
                    console.log(`${title} file is not found`)
                }
            }
        }

        for (let i = priorCheck || 0; i >= 0; i--) {
            await process(moment().add(-1*i, 'd'))
        }
    }
}

let ellenWhiteAudio = async function () {
    await dailyAudio(
        "en",
        "EGW notes",
        {
            artist: "Ellen G. White Notes",
            image: "misc/ellen-white-notes.png",
            imageRatio: "square",
            tracks: []
        },
        function (targetDate, week, day) {
            return `https://egwhiteaudio.com/wp-content/uploads/${targetDate.format("YYYY/MM/YYYY-MM-DD")}.mp3`
        },
        2
    )
}

let indonesiaAudio = async function () {
    await dailyAudio(
        "in",
        "AWR Asia",
        {
            artist: "AWR Asia",
            tracks: []
        },
        function (targetDate, week, day) {
            let mapping = [7, 1, 2, 3, 4, 5, 6]
            return `https://podcasts.awr.org/Audio/Asia/LowResWeb/INDJA/SSL/INDJAaSSLx_${targetDate.format("YYYYMMDD")}_${mapping[day-1]}.mp3`
        }
    )
}

let run = async function () {
    await ellenWhiteAudio();
    await indonesiaAudio();
}

run().then(() => {
    return true
});<|endoftext|>


<commit_before>/*
 * IDBWrapper - A cross-browser wrapper for IndexedDB
 * Copyright (c) 2011 - 2012 Jens Arps
 * http://jensarps.de/
 *
 * Licensed under the MIT (X11) license
 */

"use strict";

(function (name, definition, global) {
  if (typeof define === 'function') {
    define(definition);
  } else {
    global[name] = definition();
  }
})('IDBStore', function () {

  var IDBStore;

  var defaults = {
    storeName: 'Store',
    dbVersion: 1,
    keyPath: 'id',
    autoIncrement: true,
    onStoreReady: function () {
    },
    indexes: []
  };

  IDBStore = function (kwArgs, onStoreReady) {

    function fixupConstants (object, constants) {
      for (var prop in constants) {
        if (!(prop in object))
          object[prop] = constants[prop];
      }
    }

    for(var key in defaults){
      this[key] = typeof kwArgs[key] != 'undefined' ? kwArgs[key] : defaults[key];
    }

    this.dbName = 'IDBWrapper-' + this.storeName;
    this.dbVersion = parseInt(this.dbVersion, 10);

    onStoreReady && (this.onStoreReady = onStoreReady);

    this.idb = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
    this.keyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange;

    this.consts = window.IDBTransaction || window.webkitIDBTransaction;
    fixupConstants(this.consts, {
      'READ_ONLY': 'readonly',
      'READ_WRITE': 'readwrite',
      'VERSION_CHANGE': 'versionchange'
    });

    this.cursor = window.IDBCursor || window.webkitIDBCursor;
    fixupConstants(this.cursor, {
      'NEXT': 'next',
      'NEXT_NO_DUPLICATE': 'nextunique',
      'PREV': 'prev',
      'PREV_NO_DUPLICATE': 'prevunique'
    });

    this.openDB();
  };

  IDBStore.prototype = {

    db: null,

    dbName: null,

    dbVersion: null,

    store: null,

    storeName: null,

    keyPath: null,

    autoIncrement: null,

    indexes: null,

    features: null,

    onStoreReady: null,

    openDB: function () {

      this.newVersionAPI = typeof this.idb.setVersion == 'undefined';

      if(!this.newVersionAPI){
        throw new Error('The IndexedDB implementation in this browser is outdated. Please upgrade your browser.');
      }

      var features = this.features = {};
      features.hasAutoIncrement = !window.mozIndexedDB; // TODO: Still, really?

      var openRequest = this.idb.open(this.dbName, this.dbVersion);

      openRequest.onerror = function (error) {

        var gotVersionErr = false;
        if ('error' in error.target) {
          gotVersionErr = error.target.error.name == "VersionError";
        } else if ('errorCode' in error.target) {
          gotVersionErr = error.target.errorCode == 12; // TODO: Use const
        }

        if (gotVersionErr) {
          console.error('Could not open database, version error:', error);
        } else {
          console.error('Could not open database, error:', error);
        }
      }.bind(this);

      openRequest.onsuccess = function (event) {

        if(this.db){
          this.onStoreReady();
          return;
        }

        this.db = event.target.result;

        if(this.db.objectStoreNames.contains(this.storeName)){
          if(!this.store){
            var emptyTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
            this.store = emptyTransaction.objectStore(this.storeName);
          }
          // check indexes

          this.indexes.forEach(function(indexData){
            var indexName = indexData.name;

            // normalize and provide existing keys
            indexData.keyPath = indexData.keyPath || indexName;
            indexData.unique = !!indexData.unique;
            indexData.multiEntry = !!indexData.multiEntry;

            if(!indexName){
              throw new Error('Cannot create index: No index name given.');
            }

            if(this.hasIndex(indexName)){
              // check if it complies
              var actualIndex = this.store.index(indexName);
              var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
                // IE10 returns undefined for no multiEntry
                if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
                  return true;
                }
                return indexData[key] == actualIndex[key];
              });
              if(!complies){
                throw new Error('Cannot modify index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
              }
            } else {
              throw new Error('Cannot create new index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
            }

          }, this);

          this.onStoreReady();
        } else {
          // We should never get here.
          throw new Error('Cannot create a new store for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
        }
      }.bind(this);

      openRequest.onupgradeneeded = function(/* IDBVersionChangeEvent */ event){

        this.db = event.target.result;

        if(this.db.objectStoreNames.contains(this.storeName)){
          this.store = event.target.transaction.objectStore(this.storeName);
        } else {
          this.store = this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
        }

        this.indexes.forEach(function(indexData){
          var indexName = indexData.name;

          // normalize and provide existing keys
          indexData.keyPath = indexData.keyPath || indexName;
          indexData.unique = !!indexData.unique;
          indexData.multiEntry = !!indexData.multiEntry;

          if(!indexName){
            throw new Error('Cannot create index: No index name given.');
          }

          if(this.hasIndex(indexName)){
            // check if it complies
            var actualIndex = this.store.index(indexName);
            var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
              // IE10 returns undefined for no multiEntry
              if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
                return true;
              }
              return indexData[key] == actualIndex[key];
            });
            if(!complies){
              // index differs, need to delete and re-create
              this.store.deleteIndex(indexName);
              this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
            }
          } else {
            this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
          }

        }, this);

      }.bind(this);
    },

    deleteDatabase: function () {
      if (this.idb.deleteDatabase) {
        this.idb.deleteDatabase(this.dbName);
      }
    },

    /*********************
     * data manipulation *
     *********************/

    put: function (dataObj, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not write data.', error);
      });
      onSuccess || (onSuccess = noop);
      if (typeof dataObj[this.keyPath] == 'undefined' && !this.features.hasAutoIncrement) {
        dataObj[this.keyPath] = this._getUID();
      }
      var putTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
      var putRequest = putTransaction.objectStore(this.storeName).put(dataObj);
      putRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      putRequest.onerror = onError;
    },

    get: function (key, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not read data.', error);
      });
      onSuccess || (onSuccess = noop);
      var getTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
      var getRequest = getTransaction.objectStore(this.storeName).get(key);
      getRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      getRequest.onerror = onError;
    },

    remove: function (key, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not remove data.', error);
      });
      onSuccess || (onSuccess = noop);
      var removeTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
      var deleteRequest = removeTransaction.objectStore(this.storeName).delete(key);
      deleteRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      deleteRequest.onerror = onError;
    },

    getAll: function (onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not read data.', error);
      });
      onSuccess || (onSuccess = noop);
      var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
      var store = getAllTransaction.objectStore(this.storeName);
      if (store.getAll) {
        var getAllRequest = store.getAll();
        getAllRequest.onsuccess = function (event) {
          onSuccess(event.target.result);
        };
        getAllRequest.onerror = onError;
      } else {
        this._getAllCursor(getAllTransaction, onSuccess, onError);
      }
    },

    _getAllCursor: function (tr, onSuccess, onError) {
      var all = [];
      var store = tr.objectStore(this.storeName);
      var cursorRequest = store.openCursor();

      cursorRequest.onsuccess = function (event) {
        var cursor = event.target.result;
        if (cursor) {
          all.push(cursor.value);
          cursor['continue']();
        }
        else {
          onSuccess(all);
        }
      };
      cursorRequest.onError = onError;
    },

    clear: function (onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not clear store.', error);
      });
      onSuccess || (onSuccess = noop);
      var clearTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
      var clearRequest = clearTransaction.objectStore(this.storeName).clear();
      clearRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      clearRequest.onerror = onError;
    },

    _getUID: function () {
      // FF bails at times on non-numeric ids. So we take an even
      // worse approach now, using current time as id. Sigh.
      return +new Date();
    },

    /************
     * indexing *
     ************/

    getIndexList: function () {
      return this.store.indexNames;
    },

    hasIndex: function (indexName) {
      return this.store.indexNames.contains(indexName);
    },

    /**********
     * cursor *
     **********/

    iterate: function (onItem, options) {
      options = mixin({
        index: null,
        order: 'ASC',
        filterDuplicates: false,
        keyRange: null,
        writeAccess: false,
        onEnd: null,
        onError: function (error) {
          console.error('Could not open cursor.', error);
        }
      }, options || {});

      var directionType = options.order.toLowerCase() == 'desc' ? 'PREV' : 'NEXT';
      if (options.filterDuplicates) {
        directionType += '_NO_DUPLICATE';
      }

      var cursorTransaction = this.db.transaction([this.storeName], this.consts[options.writeAccess ? 'READ_WRITE' : 'READ_ONLY']);
      var cursorTarget = cursorTransaction.objectStore(this.storeName);
      if (options.index) {
        cursorTarget = cursorTarget.index(options.index);
      }

      var cursorRequest = cursorTarget.openCursor(options.keyRange, this.cursor[directionType]);
      cursorRequest.onerror = options.onError;
      cursorRequest.onsuccess = function (event) {
        var cursor = event.target.result;
        if (cursor) {
          onItem(cursor.value, cursor, cursorTransaction);
          cursor['continue']();
        } else {
          if(options.onEnd){
            options.onEnd()
          } else {
            onItem(null);
          }
        }
      };
    },

    count: function (onSuccess, options) {

      options = mixin({
        index: null,
        keyRange: null
      }, options || {});

      var onError = options.onError || function (error) {
        console.error('Could not open cursor.', error);
      };

      var cursorTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
      var cursorTarget = cursorTransaction.objectStore(this.storeName);
      if (options.index) {
        cursorTarget = cursorTarget.index(options.index);
      }

      var countRequest = cursorTarget.count(options.keyRange);
      countRequest.onsuccess = function (evt) {
        onSuccess(evt.target.result);
      };
      countRequest.onError = function (error) {
        onError(error);
      };
    },

    /**************/
    /* key ranges */
    /**************/

    makeKeyRange: function(options){
      var keyRange,
          hasLower = typeof options.lower != 'undefined',
          hasUpper = typeof options.upper != 'undefined';

      switch(true){
        case hasLower && hasUpper:
          keyRange = this.keyRange.bound(options.lower, options.upper, options.excludeLower, options.excludeUpper);
          break;
        case hasLower:
          keyRange = this.keyRange.lowerBound(options.lower, options.excludeLower);
          break;
        case hasUpper:
          keyRange = this.keyRange.upperBound(options.upper, options.excludeUpper);
          break;
        default:
          throw new Error('Cannot create KeyRange. Provide one or both of "lower" or "upper" value.');
          break;
      }

      return keyRange;

    }

  };

  /** helpers **/

  var noop = function () {
  };
  var empty = {};
  var mixin = function (target, source) {
    var name, s;
    for (name in source) {
      s = source[name];
      if (s !== empty[name] && s !== target[name]) {
        target[name] = s;
      }
    }
    return target;
  };

  return IDBStore;

}, this);
<commit_msg>support commonJS<commit_after>/*
 * IDBWrapper - A cross-browser wrapper for IndexedDB
 * Copyright (c) 2011 - 2012 Jens Arps
 * http://jensarps.de/
 *
 * Licensed under the MIT (X11) license
 */

"use strict";

(function (name, definition, global) {
  if (typeof define === 'function') {
    define(definition);
  } else if (typeof module !== 'undefined' && module.exports) {
    module.exports = definition();
  } else {
    global[name] = definition();
  }
})('IDBStore', function () {

  var IDBStore;

  var defaults = {
    storeName: 'Store',
    dbVersion: 1,
    keyPath: 'id',
    autoIncrement: true,
    onStoreReady: function () {
    },
    indexes: []
  };

  IDBStore = function (kwArgs, onStoreReady) {

    function fixupConstants (object, constants) {
      for (var prop in constants) {
        if (!(prop in object))
          object[prop] = constants[prop];
      }
    }

    for(var key in defaults){
      this[key] = typeof kwArgs[key] != 'undefined' ? kwArgs[key] : defaults[key];
    }

    this.dbName = 'IDBWrapper-' + this.storeName;
    this.dbVersion = parseInt(this.dbVersion, 10);

    onStoreReady && (this.onStoreReady = onStoreReady);

    this.idb = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
    this.keyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange;

    this.consts = window.IDBTransaction || window.webkitIDBTransaction;
    fixupConstants(this.consts, {
      'READ_ONLY': 'readonly',
      'READ_WRITE': 'readwrite',
      'VERSION_CHANGE': 'versionchange'
    });

    this.cursor = window.IDBCursor || window.webkitIDBCursor;
    fixupConstants(this.cursor, {
      'NEXT': 'next',
      'NEXT_NO_DUPLICATE': 'nextunique',
      'PREV': 'prev',
      'PREV_NO_DUPLICATE': 'prevunique'
    });

    this.openDB();
  };

  IDBStore.prototype = {

    db: null,

    dbName: null,

    dbVersion: null,

    store: null,

    storeName: null,

    keyPath: null,

    autoIncrement: null,

    indexes: null,

    features: null,

    onStoreReady: null,

    openDB: function () {

      this.newVersionAPI = typeof this.idb.setVersion == 'undefined';

      if(!this.newVersionAPI){
        throw new Error('The IndexedDB implementation in this browser is outdated. Please upgrade your browser.');
      }

      var features = this.features = {};
      features.hasAutoIncrement = !window.mozIndexedDB; // TODO: Still, really?

      var openRequest = this.idb.open(this.dbName, this.dbVersion);

      openRequest.onerror = function (error) {

        var gotVersionErr = false;
        if ('error' in error.target) {
          gotVersionErr = error.target.error.name == "VersionError";
        } else if ('errorCode' in error.target) {
          gotVersionErr = error.target.errorCode == 12; // TODO: Use const
        }

        if (gotVersionErr) {
          console.error('Could not open database, version error:', error);
        } else {
          console.error('Could not open database, error:', error);
        }
      }.bind(this);

      openRequest.onsuccess = function (event) {

        if(this.db){
          this.onStoreReady();
          return;
        }

        this.db = event.target.result;

        if(this.db.objectStoreNames.contains(this.storeName)){
          if(!this.store){
            var emptyTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
            this.store = emptyTransaction.objectStore(this.storeName);
          }
          // check indexes

          this.indexes.forEach(function(indexData){
            var indexName = indexData.name;

            // normalize and provide existing keys
            indexData.keyPath = indexData.keyPath || indexName;
            indexData.unique = !!indexData.unique;
            indexData.multiEntry = !!indexData.multiEntry;

            if(!indexName){
              throw new Error('Cannot create index: No index name given.');
            }

            if(this.hasIndex(indexName)){
              // check if it complies
              var actualIndex = this.store.index(indexName);
              var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
                // IE10 returns undefined for no multiEntry
                if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
                  return true;
                }
                return indexData[key] == actualIndex[key];
              });
              if(!complies){
                throw new Error('Cannot modify index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
              }
            } else {
              throw new Error('Cannot create new index "' + indexName + '" for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
            }

          }, this);

          this.onStoreReady();
        } else {
          // We should never get here.
          throw new Error('Cannot create a new store for current version. Please bump version number to ' + ( this.dbVersion + 1 ) + '.');
        }
      }.bind(this);

      openRequest.onupgradeneeded = function(/* IDBVersionChangeEvent */ event){

        this.db = event.target.result;

        if(this.db.objectStoreNames.contains(this.storeName)){
          this.store = event.target.transaction.objectStore(this.storeName);
        } else {
          this.store = this.db.createObjectStore(this.storeName, { keyPath: this.keyPath, autoIncrement: this.autoIncrement});
        }

        this.indexes.forEach(function(indexData){
          var indexName = indexData.name;

          // normalize and provide existing keys
          indexData.keyPath = indexData.keyPath || indexName;
          indexData.unique = !!indexData.unique;
          indexData.multiEntry = !!indexData.multiEntry;

          if(!indexName){
            throw new Error('Cannot create index: No index name given.');
          }

          if(this.hasIndex(indexName)){
            // check if it complies
            var actualIndex = this.store.index(indexName);
            var complies = ['keyPath', 'unique', 'multiEntry'].every(function(key){
              // IE10 returns undefined for no multiEntry
              if (key == 'multiEntry' && actualIndex[key] === undefined && indexData[key] === false) {
                return true;
              }
              return indexData[key] == actualIndex[key];
            });
            if(!complies){
              // index differs, need to delete and re-create
              this.store.deleteIndex(indexName);
              this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
            }
          } else {
            this.store.createIndex(indexName, indexData.keyPath, { unique: indexData.unique, multiEntry: indexData.multiEntry });
          }

        }, this);

      }.bind(this);
    },

    deleteDatabase: function () {
      if (this.idb.deleteDatabase) {
        this.idb.deleteDatabase(this.dbName);
      }
    },

    /*********************
     * data manipulation *
     *********************/

    put: function (dataObj, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not write data.', error);
      });
      onSuccess || (onSuccess = noop);
      if (typeof dataObj[this.keyPath] == 'undefined' && !this.features.hasAutoIncrement) {
        dataObj[this.keyPath] = this._getUID();
      }
      var putTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
      var putRequest = putTransaction.objectStore(this.storeName).put(dataObj);
      putRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      putRequest.onerror = onError;
    },

    get: function (key, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not read data.', error);
      });
      onSuccess || (onSuccess = noop);
      var getTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
      var getRequest = getTransaction.objectStore(this.storeName).get(key);
      getRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      getRequest.onerror = onError;
    },

    remove: function (key, onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not remove data.', error);
      });
      onSuccess || (onSuccess = noop);
      var removeTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
      var deleteRequest = removeTransaction.objectStore(this.storeName).delete(key);
      deleteRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      deleteRequest.onerror = onError;
    },

    getAll: function (onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not read data.', error);
      });
      onSuccess || (onSuccess = noop);
      var getAllTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
      var store = getAllTransaction.objectStore(this.storeName);
      if (store.getAll) {
        var getAllRequest = store.getAll();
        getAllRequest.onsuccess = function (event) {
          onSuccess(event.target.result);
        };
        getAllRequest.onerror = onError;
      } else {
        this._getAllCursor(getAllTransaction, onSuccess, onError);
      }
    },

    _getAllCursor: function (tr, onSuccess, onError) {
      var all = [];
      var store = tr.objectStore(this.storeName);
      var cursorRequest = store.openCursor();

      cursorRequest.onsuccess = function (event) {
        var cursor = event.target.result;
        if (cursor) {
          all.push(cursor.value);
          cursor['continue']();
        }
        else {
          onSuccess(all);
        }
      };
      cursorRequest.onError = onError;
    },

    clear: function (onSuccess, onError) {
      onError || (onError = function (error) {
        console.error('Could not clear store.', error);
      });
      onSuccess || (onSuccess = noop);
      var clearTransaction = this.db.transaction([this.storeName], this.consts.READ_WRITE);
      var clearRequest = clearTransaction.objectStore(this.storeName).clear();
      clearRequest.onsuccess = function (event) {
        onSuccess(event.target.result);
      };
      clearRequest.onerror = onError;
    },

    _getUID: function () {
      // FF bails at times on non-numeric ids. So we take an even
      // worse approach now, using current time as id. Sigh.
      return +new Date();
    },

    /************
     * indexing *
     ************/

    getIndexList: function () {
      return this.store.indexNames;
    },

    hasIndex: function (indexName) {
      return this.store.indexNames.contains(indexName);
    },

    /**********
     * cursor *
     **********/

    iterate: function (onItem, options) {
      options = mixin({
        index: null,
        order: 'ASC',
        filterDuplicates: false,
        keyRange: null,
        writeAccess: false,
        onEnd: null,
        onError: function (error) {
          console.error('Could not open cursor.', error);
        }
      }, options || {});

      var directionType = options.order.toLowerCase() == 'desc' ? 'PREV' : 'NEXT';
      if (options.filterDuplicates) {
        directionType += '_NO_DUPLICATE';
      }

      var cursorTransaction = this.db.transaction([this.storeName], this.consts[options.writeAccess ? 'READ_WRITE' : 'READ_ONLY']);
      var cursorTarget = cursorTransaction.objectStore(this.storeName);
      if (options.index) {
        cursorTarget = cursorTarget.index(options.index);
      }

      var cursorRequest = cursorTarget.openCursor(options.keyRange, this.cursor[directionType]);
      cursorRequest.onerror = options.onError;
      cursorRequest.onsuccess = function (event) {
        var cursor = event.target.result;
        if (cursor) {
          onItem(cursor.value, cursor, cursorTransaction);
          cursor['continue']();
        } else {
          if(options.onEnd){
            options.onEnd()
          } else {
            onItem(null);
          }
        }
      };
    },

    count: function (onSuccess, options) {

      options = mixin({
        index: null,
        keyRange: null
      }, options || {});

      var onError = options.onError || function (error) {
        console.error('Could not open cursor.', error);
      };

      var cursorTransaction = this.db.transaction([this.storeName], this.consts.READ_ONLY);
      var cursorTarget = cursorTransaction.objectStore(this.storeName);
      if (options.index) {
        cursorTarget = cursorTarget.index(options.index);
      }

      var countRequest = cursorTarget.count(options.keyRange);
      countRequest.onsuccess = function (evt) {
        onSuccess(evt.target.result);
      };
      countRequest.onError = function (error) {
        onError(error);
      };
    },

    /**************/
    /* key ranges */
    /**************/

    makeKeyRange: function(options){
      var keyRange,
          hasLower = typeof options.lower != 'undefined',
          hasUpper = typeof options.upper != 'undefined';

      switch(true){
        case hasLower && hasUpper:
          keyRange = this.keyRange.bound(options.lower, options.upper, options.excludeLower, options.excludeUpper);
          break;
        case hasLower:
          keyRange = this.keyRange.lowerBound(options.lower, options.excludeLower);
          break;
        case hasUpper:
          keyRange = this.keyRange.upperBound(options.upper, options.excludeUpper);
          break;
        default:
          throw new Error('Cannot create KeyRange. Provide one or both of "lower" or "upper" value.');
          break;
      }

      return keyRange;

    }

  };

  /** helpers **/

  var noop = function () {
  };
  var empty = {};
  var mixin = function (target, source) {
    var name, s;
    for (name in source) {
      s = source[name];
      if (s !== empty[name] && s !== target[name]) {
        target[name] = s;
      }
    }
    return target;
  };

  return IDBStore;

}, this);
<|endoftext|>


<commit_before>'use strict';

const defaultByteLength = 1024 * 8;
const charArray = [];

class IOBuffer {
    constructor(data, options) {
        options = options || {};
        var dataIsGiven = false;
        if (data === undefined) {
            data = defaultByteLength;
        }
        if (typeof data === 'number') {
            data = new ArrayBuffer(data);
        } else {
            dataIsGiven = true;
            this._lastWrittenByte = data.byteLength;
        }

        let length = data.byteLength;
        const offset = options.offset ? options.offset>>>0 : 0;
        if (data.buffer) {
            length = data.byteLength - offset;
            if (data.byteLength !== data.buffer.byteLength) { // Node.js buffer from pool
                data = data.buffer.slice(data.byteOffset + offset, data.byteOffset + data.byteLength);
            } else if (offset) {
                data = data.buffer.slice(offset);
            } else {
                data = data.buffer;
            }
        }
        if(dataIsGiven) {
            this._lastWrittenByte = length;
        } else {
            this._lastWrittenByte = 0;
        }
        this.buffer = data;
        this.length = length;
        this.byteLength = length;
        this.byteOffset = 0;
        this.offset = 0;
        this.littleEndian = true;
        this._data = new DataView(this.buffer);
        this._increment = length || defaultByteLength;
        this._mark = 0;
        this._marks = [];
    }

    available(byteLength) {
        if (byteLength === undefined) byteLength = 1;
        return (this.offset + byteLength) <= this.length;
    }

    isLittleEndian() {
        return this.littleEndian;
    }

    setLittleEndian() {
        this.littleEndian = true;
    }

    isBigEndian() {
        return !this.littleEndian;
    }

    setBigEndian() {
        this.littleEndian = false;
    }

    skip(n) {
        if (n === undefined) n = 1;
        this.offset += n;
        return this;
    }

    seek(offset) {
        this.offset = offset;
        return this;
    }

    mark() {
        this._mark = this.offset;
        return this;
    }

    pushMark() {
        this._marks.push(this.offset);
        return this;
    }

    popMark() {
        const offset = this._marks.pop();
        if(offset === undefined) throw new Error('Mark stack empty');
        this.seek(offset);
        return this;
    }

    reset() {
        this.offset = this._mark;
        return this;
    }

    rewind() {
        this.offset = 0;
        return this;
    }

    ensureAvailable(byteLength) {
        if (byteLength === undefined) byteLength = 1;
        if (!this.available(byteLength)) {
            const newIncrement = this._increment + this._increment;
            this._increment = newIncrement;
            const newLength = this.length + newIncrement;
            const newArray = new Uint8Array(newLength);
            newArray.set(new Uint8Array(this.buffer));
            this.buffer = newArray.buffer;
            this.length = newLength;
            this._data = new DataView(this.buffer);
        }
        return this;
    }

    readBoolean() {
        return this.readUint8() !== 0;
    }

    readInt8() {
        return this._data.getInt8(this.offset++);
    }

    readUint8() {
        return this._data.getUint8(this.offset++);
    }

    readByte() {
        return this.readUint8();
    }

    readBytes(n) {
        if (n === undefined) n = 1;
        var bytes = new Uint8Array(n);
        for (var i = 0; i < n; i++) {
            bytes[i] = this.readByte();
        }
        return bytes;
    }

    readInt16() {
        var value = this._data.getInt16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
    }

    readUint16() {
        var value = this._data.getUint16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
    }

    readInt32() {
        var value = this._data.getInt32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    readUint32() {
        var value = this._data.getUint32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    readFloat32() {
        var value = this._data.getFloat32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    readFloat64() {
        var value = this._data.getFloat64(this.offset, this.littleEndian);
        this.offset += 8;
        return value;
    }

    readChar() {
        return String.fromCharCode(this.readInt8());
    }

    readChars(n) {
        if (n === undefined) n = 1;
        charArray.length = n;
        for (var i = 0; i < n; i++) {
            charArray[i] = this.readChar();
        }
        return charArray.join('');
    }

    writeBoolean(bool) {
        this.writeUint8(bool ? 0xff : 0x00);
        return this;
    }

    writeInt8(value) {
        this.ensureAvailable(1);
        this._data.setInt8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
    }

    writeUint8(value) {
        this.ensureAvailable(1);
        this._data.setUint8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
    }

    writeByte(value) {
        return this.writeUint8(value);
    }

    writeBytes(bytes) {
        this.ensureAvailable(bytes.length);
        for (var i = 0; i < bytes.length; i++) {
            this._data.setUint8(this.offset++, bytes[i]);
        }
        this._updateLastWrittenByte();
        return this;
    }

    writeInt16(value) {
        this.ensureAvailable(2);
        this._data.setInt16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
    }

    writeUint16(value) {
        this.ensureAvailable(2);
        this._data.setUint16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
    }

    writeInt32(value) {
        this.ensureAvailable(4);
        this._data.setInt32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    writeUint32(value) {
        this.ensureAvailable(4);
        this._data.setUint32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    writeFloat32(value) {
        this.ensureAvailable(4);
        this._data.setFloat32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    writeFloat64(value) {
        this.ensureAvailable(8);
        this._data.setFloat64(this.offset, value, this.littleEndian);
        this.offset += 8;
        this._updateLastWrittenByte();
        return this;
    }

    writeChar(str) {
        return this.writeUint8(str.charCodeAt(0));
    }

    writeChars(str) {
        for (var i = 0; i < str.length; i++) {
            this.writeUint8(str.charCodeAt(i));
        }
        return this;
    }

    toArray() {
        return new Uint8Array(this.buffer, 0, this._lastWrittenByte);
    }

    _updateLastWrittenByte() {
        if(this.offset > this._lastWrittenByte) {
            this._lastWrittenByte = this.offset;
        }
    }
}

module.exports = IOBuffer;
<commit_msg>Add jsdoc documentation<commit_after>'use strict';

const defaultByteLength = 1024 * 8;
const charArray = [];

/**
 * IOBuffer
 * @constructor
 * @param {undefined|number|ArrayBuffer|TypedArray|IOBuffer} data - The data to construct the IOBuffer with.
 * If it's a number it will initialize the buffer with the number as the buffer's length
 * If it's undefined it will initialize the buffer with a default length of 8 Kb
 * If its an ArrayBuffer, a typed array or an IOBuffer instance, it will create a copy of the buffer
 * @param {object} options - An option object
 * @param {number} [option.offset=0} - copy the input data ignoring the first n bytes
 * @property {ArrayBuffer} buffer - Reference to the internal ArrayBuffer object
 * @property {number} length - Byte length of the internal ArrayBuffer
 * @property {number} offset - The current offset of the buffer's pointer
 */
class IOBuffer {
    constructor(data, options) {
        options = options || {};
        var dataIsGiven = false;
        if (data === undefined) {
            data = defaultByteLength;
        }
        if (typeof data === 'number') {
            data = new ArrayBuffer(data);
        } else {
            dataIsGiven = true;
            this._lastWrittenByte = data.byteLength;
        }

        let length = data.byteLength;
        const offset = options.offset ? options.offset>>>0 : 0;
        if (data.buffer) {
            length = data.byteLength - offset;
            if (data.byteLength !== data.buffer.byteLength) { // Node.js buffer from pool
                data = data.buffer.slice(data.byteOffset + offset, data.byteOffset + data.byteLength);
            } else if (offset) {
                data = data.buffer.slice(offset);
            } else {
                data = data.buffer;
            }
        }
        if(dataIsGiven) {
            this._lastWrittenByte = length;
        } else {
            this._lastWrittenByte = 0;
        }
        this.buffer = data;
        this.length = length;
        this.byteLength = length;
        this.byteOffset = 0;
        this.offset = 0;
        this.littleEndian = true;
        this._data = new DataView(this.buffer);
        this._increment = length || defaultByteLength;
        this._mark = 0;
        this._marks = [];
    }

    /**
     * Checks if the memory allocated to the buffer is sufficient to store more bytes after the offset
     * @param {number} [byteLength=1] The needed memory in bytes
     * @returns {boolean} Returns true if there is sufficient space and false otherwise
     */
    available(byteLength) {
        if (byteLength === undefined) byteLength = 1;
        return (this.offset + byteLength) <= this.length;
    }

    /**
     * Check if little-endian mode is used for reading and writing multi-byte values
     * @returns {boolean} Returns true if little-endian mode is used, false otherwise
     */
    isLittleEndian() {
        return this.littleEndian;
    }

    /**
     * Set little-endian mode for reading and writing multi-byte values
     * @returns {IOBuffer}
     */
    setLittleEndian() {
        this.littleEndian = true;
    }

    /**
     * Check if big-endian mode is used for reading and writing multi-byte values
     * @returns {boolean} Returns true if big-endian mode is used, false otherwise
     */
    isBigEndian() {
        return !this.littleEndian;
    }

    /**
     * Switches to big-endian mode for reading and writing multi-byte values
     * @returns {IOBuffer}
     */
    setBigEndian() {
        this.littleEndian = false;
    }

    /**
     * Move the pointer n bytes forward
     * @param {number} n
     * @returns {IOBuffer}
     */
    skip(n) {
        if (n === undefined) n = 1;
        this.offset += n;
        return this;
    }

    /**
     * Move the pointer to the given offset
     * @param {number} offset
     * @returns {IOBuffer}
     */
    seek(offset) {
        this.offset = offset;
        return this;
    }

    /**
     * Store the current pointer offset.
     * @see {@link IOBuffer#reset}
     * @returns {IOBuffer}
     */
    mark() {
        this._mark = this.offset;
        return this;
    }

    /**
     * Move the pointer back to the last pointer offset set by mark
     * @see {@link IOBuffer#mark}
     * @returns {IOBuffer}
     */
    reset() {
        this.offset = this._mark;
        return this;
    }

    /**
     * Push the current pointer offset to the mark stack
     * @see {@link IOBuffer#popMark}
     * @returns {IOBuffer}
     */
    pushMark() {
        this._marks.push(this.offset);
        return this;
    }

    /**
     * Pop the last pointer offset from the mark stack, and set the current pointer offset to the popped value
     * @see {@link IOBuffer#pushMark}
     * @returns {IOBuffer}
     */
    popMark() {
        const offset = this._marks.pop();
        if(offset === undefined) throw new Error('Mark stack empty');
        this.seek(offset);
        return this;
    }

    /**
     * Move the pointer offset back to 0
     * @returns {IOBuffer}
     */
    rewind() {
        this.offset = 0;
        return this;
    }

    /**
     * Make sure the buffer has sufficient memory to write a given byteLength at the current pointer offset
     * If the buffer's memory is insufficient, this method will double the internal buffer's length
     * @param {number} byteLength
     * @returns {IOBuffer}
     */
    ensureAvailable(byteLength) {
        if (byteLength === undefined) byteLength = 1;
        if (!this.available(byteLength)) {
            const newIncrement = this._increment + this._increment;
            this._increment = newIncrement;
            const newLength = this.length + newIncrement;
            const newArray = new Uint8Array(newLength);
            newArray.set(new Uint8Array(this.buffer));
            this.buffer = newArray.buffer;
            this.length = newLength;
            this._data = new DataView(this.buffer);
        }
        return this;
    }

    /**
     * Read a byte and return false if the byte's value is 0, or true otherwise
     * Moves pointer forward
     * @returns {boolean}
     */
    readBoolean() {
        return this.readUint8() !== 0;
    }

    /**
     * Read a signed 8-bit integer and move pointer forward
     * @returns {number}
     */
    readInt8() {
        return this._data.getInt8(this.offset++);
    }

    /**
     * Read an unsigned 8-bit integer and move pointer forward
     * @returns {number}
     */
    readUint8() {
        return this._data.getUint8(this.offset++);
    }

    /**
     * Alias for {@link IOBuffer#readUint8}
     * @returns {number}
     */
    readByte() {
        return this.readUint8();
    }

    /**
     * Read n bytes and move pointer forward.
     * @param n
     * @returns {Uint8Array}
     */
    readBytes(n) {
        if (n === undefined) n = 1;
        var bytes = new Uint8Array(n);
        for (var i = 0; i < n; i++) {
            bytes[i] = this.readByte();
        }
        return bytes;
    }

    /**
     * Read a 16-bit signed integer and move pointer forward
     * @returns {number}
     */
    readInt16() {
        var value = this._data.getInt16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
    }

    /**
     * Read a 16-bit unsigned integer and move pointer forward
     * @returns {number}
     */
    readUint16() {
        var value = this._data.getUint16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
    }

    /**
     * Read a 32-bit signed integer and move pointer forward
     * @returns {number}
     */
    readInt32() {
        var value = this._data.getInt32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 32-bit unsigned integer and move pointer forward
     * @returns {number}
     */
    readUint32() {
        var value = this._data.getUint32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 32-bit floating number and move pointer forward
     * @returns {number}
     */
    readFloat32() {
        var value = this._data.getFloat32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 64-bit floating number and move pointer forward
     * @returns {number}
     */
    readFloat64() {
        var value = this._data.getFloat64(this.offset, this.littleEndian);
        this.offset += 8;
        return value;
    }

    /**
     * Read 1-byte ascii character and move pointer forward
     * @returns {string}
     */
    readChar() {
        return String.fromCharCode(this.readInt8());
    }

    /**
     * Read n 1-byte ascii characters and move pointer forward
     * @param n
     * @returns {string}
     */
    readChars(n) {
        if (n === undefined) n = 1;
        charArray.length = n;
        for (var i = 0; i < n; i++) {
            charArray[i] = this.readChar();
        }
        return charArray.join('');
    }

    /**
     * Write 0xff if the passed value is truthy, 0x00 otherwise
     * @param {any} value
     * @returns {IOBuffer}
     */
    writeBoolean(value) {
        this.writeUint8(value ? 0xff : 0x00);
        return this;
    }

    /**
     * Write value as an 8-bit signed integer
     * @param value
     * @returns {IOBuffer}
     */
    writeInt8(value) {
        this.ensureAvailable(1);
        this._data.setInt8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as a 8-bit unsigned integer
     * @param value
     * @returns {IOBuffer}
     */
    writeUint8(value) {
        this.ensureAvailable(1);
        this._data.setUint8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * An alias for {@link IOBuffer#writeUint8}
     * @param value
     * @returns {IOBuffer}
     */
    writeByte(value) {
        return this.writeUint8(value);
    }

    /**
     * Write bytes
     * @param {Array|Uint8Array} bytes
     * @returns {IOBuffer}
     */
    writeBytes(bytes) {
        this.ensureAvailable(bytes.length);
        for (var i = 0; i < bytes.length; i++) {
            this._data.setUint8(this.offset++, bytes[i]);
        }
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as an 16-bit signed integer
     * @param value
     * @returns {IOBuffer}
     */
    writeInt16(value) {
        this.ensureAvailable(2);
        this._data.setInt16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as a 16-bit unsigned integer
     * @param value
     * @returns {IOBuffer}
     */
    writeUint16(value) {
        this.ensureAvailable(2);
        this._data.setUint16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit signed integer at the current pointer offset
     * @param value
     * @returns {IOBuffer}
     */
    writeInt32(value) {
        this.ensureAvailable(4);
        this._data.setInt32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit unsigned integer at the current pointer offset
     * @param value - The value to set
     * @returns {IOBuffer}
     */
    writeUint32(value) {
        this.ensureAvailable(4);
        this._data.setUint32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit floating number at the current pointer offset
     * @param value - The value to set
     * @returns {IOBuffer}
     */
    writeFloat32(value) {
        this.ensureAvailable(4);
        this._data.setFloat32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 64-bit floating number at the current pointer offset
     * @param value
     * @returns {IOBuffer}
     */
    writeFloat64(value) {
        this.ensureAvailable(8);
        this._data.setFloat64(this.offset, value, this.littleEndian);
        this.offset += 8;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write the charCode of the passed string's first character to the current pointer offset
     * @param {string} str - The character to set
     * @returns {IOBuffer}
     */
    writeChar(str) {
        return this.writeUint8(str.charCodeAt(0));
    }

    /**
     * Write the charCodes of the passed string's characters to the current pointer offset
     * @param str
     * @returns {IOBuffer}
     */
    writeChars(str) {
        for (var i = 0; i < str.length; i++) {
            this.writeUint8(str.charCodeAt(i));
        }
        return this;
    }

    /**
     * Export a view of the internal buffer. The view spans from offset 0 to the largest written offset
     * @returns {Uint8Array}
     */
    toArray() {
        return new Uint8Array(this.buffer, 0, this._lastWrittenByte);
    }

    /**
     * Update the last written byte offset
     * @private
     */
    _updateLastWrittenByte() {
        if(this.offset > this._lastWrittenByte) {
            this._lastWrittenByte = this.offset;
        }
    }
}

module.exports = IOBuffer;
<|endoftext|>