<commit_before>(function($) {
    /**
     * はてなブログのカテゴリ一覧モジュールを階層っぽくしてぱかぱかするやつ
     *
     * @author xxxkurosukexxx <info [at] kurosuke.org>
     * @license MIT
     *
     * Example:
     *
     * $('.hatena-module-category').pakapaka({
     *     config: [
     *         { regex: /^\[C\]/, title: 'Camera', open: true },
     *         { regex: /^\[F\]/, title: 'Films' }
     *     ]
     * });
     *
     */

    $.fn.pakapaka = function (option) {
        var $self = $.fn.pakapaka,
            $this = this,
            config,
            _config,
            $_target,
            $parentLi = $('

')
                            .append($('
▼▶
'))
                            .append($('').addClass('pakapakaParent')),
            $ulTpl = $('

').addClass('pakapaka'),
            $_parentLi,
            $_ul;

        if (option.config === undefined || ! $.isArray(option.config)) {
            alert('PakaPaka!!!');
            return this;
        }

        config = option.config;

        return $this.each(function () {
            $_target = $(this);

            $.each(config, function() {
                _c = this;
                $_ul = $ulTpl.clone(true);

                $_target.find('li').each(function () {
                    $_li = $(this);
                    _lT = $_li.text().trim();

                    if (_lT.match(_c.regex)) {
                        $_ul.append($_li.clone(true));
                        $_li.remove();
                    }

                }); //---each-li

                $_parentLi = $parentLi.clone(true)
                                .find('.pakapakaParent').text(_c.name).end()
                                .append($_ul)
                                .click(function () {
                                    $(this).find('ul.pakapaka').slideToggle();
                                    $(this).find('.archive-module-button span').toggle();
                                });
                if (_c.open !== undefined && _c.open === true) {
                    $_parentLi
                        .find('.archive-module-hide-button').show().end()
                        .find('.archive-module-show-button').hide().end()
                        .find('ul.pakapaka').show();
                } else {
                    $_parentLi
                        .find('.archive-module-hide-button').hide().end()
                        .find('.archive-module-show-button').show().end()
                        .find('ul.pakapaka').hide();
                }

                $_target.find('ul.hatena-urllist').append($_parentLi);

            }); //---each-config

        }); //---each-this

    };

})(jQuery);
<commit_msg>修正...<commit_after>(function($) {
    /**
     * はてなブログのカテゴリ一覧モジュールを階層っぽくしてぱかぱかするやつ
     *
     * @author xxxkurosukexxx <info [at] kurosuke.org>
     * @license MIT
     *
     * Example:
     *
     * $('.hatena-module-category').pakapaka({
     *     config: [
     *         { regex: /^\[C\]/, title: 'Camera', open: true },
     *         { regex: /^\[F\]/, title: 'Films' }
     *     ]
     * });
     *
     */

    $.fn.pakapaka = function (option) {
        var $this = this,
            config,
            _config,
            $_target,
            $parentLi = $('

')
                            .append($('
▼▶
'))
                            .append($('').addClass('pakapakaParent')),
            $ulTpl = $('

').addClass('pakapaka'),
            $_parentLi,
            $_ul,
            $_li,
            _lT;

        if (option.config === undefined || ! $.isArray(option.config)) {
            return $this;
        }

        config = option.config;

        return $this.each(function () {
            $_target = $(this);

            $.each(config, function() {
                _config = this;
                $_ul = $ulTpl.clone(true);

                $_target.find('li').each(function () {
                    $_li = $(this);
                    _lT = $_li.text().trim();

                    if (_lT.match(_config.regex)) {
                        $_ul.append($_li.clone(true));
                        $_li.remove();
                    }

                }); //---each-li

                $_parentLi = $parentLi.clone(true)
                                .find('.pakapakaParent').text(_config.name).end()
                                .append($_ul)
                                .click(function () {
                                    $(this).find('ul.pakapaka').slideToggle();
                                    $(this).find('.archive-module-button span').toggle();
                                });
                if (_config.open !== undefined && _config.open === true) {
                    $_parentLi
                        .find('.archive-module-hide-button').show().end()
                        .find('.archive-module-show-button').hide().end()
                        .find('ul.pakapaka').show();
                } else {
                    $_parentLi
                        .find('.archive-module-hide-button').hide().end()
                        .find('.archive-module-show-button').show().end()
                        .find('ul.pakapaka').hide();
                }

                $_target.find('ul.hatena-urllist').append($_parentLi);

            }); //---each-config

        }); //---each-this

    };

})(jQuery);
<|endoftext|>


<commit_before>import Ember                        from 'ember';
import { moduleForComponent, test } from 'ember-qunit';
import hbs                          from 'htmlbars-inline-precompile';
import startMirage                  from '../../helpers/setup-mirage-for-integration';
import Mirage                       from 'ember-cli-mirage';

const {
  run: { later }
} = Ember;

moduleForComponent('new-version-notifier', 'Integration | Component | new version notifier', {
  integration: true,
  beforeEach() {
    startMirage(this.container);
  },
  afterEach() {
    window.server.shutdown();
  }
});

test('it works', function (assert) {
  assert.expect(12);

  let callCount = 0;

  this.setProperties({
    version:     null,
    lastVersion: null
  });

  window.server.get('/VERSION.txt', function(){
    ++callCount;

    return callCount < 4 ? 'v1.0.' + callCount : 'v1.0.3';
  });

  this.render(hbs`{{new-version-notifier updateInterval=100 version=version lastVersion=lastVersion}}`);

  const done = assert.async(3);

  later(() => {
    assert.equal(this.$().text().trim(), '');
    assert.equal(callCount, 1);
    assert.equal(this.get('version'), 'v1.0.1');
    assert.equal(this.get('lastVersion'), null);

    done();
  }, 0);

  later(() => {
    assert.equal(callCount, 2);
    assert.equal(this.get('version'), 'v1.0.2');
    assert.equal(this.get('lastVersion'), 'v1.0.1');
    assert.equal(this.$().text().trim().replace(/\n|\t/, ''), 'This application has been updated from version v1.0.1 to v1.0.2. Please save any work, then refresh browser to see changes. Reload      ×');

    done();
  }, 150);

  later(() => {
    assert.equal(callCount, 6);
    assert.equal(this.get('version'), 'v1.0.3');
    assert.equal(this.get('lastVersion'), 'v1.0.2');
    assert.equal(this.$().text().trim().replace(/\n|\t/, ''), 'This application has been updated from version v1.0.2 to v1.0.3. Please save any work, then refresh browser to see changes. Reload      ×');

    done();
  }, 550);
});

test('one version', function (assert) {
  assert.expect(4);

  let callCount = 0;

  this.setProperties({
    version:     null,
    lastVersion: null
  });

  window.server.get('/VERSION.txt', function(){
    ++callCount;

    return 'v1.0.3';
  });

  this.render(hbs`{{new-version-notifier updateInterval=100 version=version lastVersion=lastVersion}}`);

  const done = assert.async(1);

  later(() => {
    assert.equal(this.$().text().trim(), '');
    assert.equal(callCount, 4);
    assert.equal(this.get('version'), 'v1.0.3');
    assert.equal(this.get('lastVersion'), null);

    done();
  }, 350);
});

test('repeat on bad response', function (assert) {
  assert.expect(4);

  let callCount = 0;

  this.setProperties({
    version:     null,
    lastVersion: null
  });

  window.server.get('/VERSION.txt', function(){
    ++callCount;

    if (callCount === 1) {
      return new Mirage.Response(500, {}, { message: '' });
    }

    return 'v1.0.3';
  });

  this.render(hbs`{{new-version-notifier updateInterval=100 version=version lastVersion=lastVersion}}`);

  const done = assert.async(1);

  later(() => {
    assert.equal(this.$().text().trim(), '');
    assert.equal(callCount, 4);
    assert.equal(this.get('version'), 'v1.0.3');
    assert.equal(this.get('lastVersion'), null);

    done();
  }, 350);
});<commit_msg>Fixed test<commit_after>import Ember                        from 'ember';
import { moduleForComponent, test } from 'ember-qunit';
import hbs                          from 'htmlbars-inline-precompile';
import startMirage                  from '../../helpers/setup-mirage-for-integration';
import Mirage                       from 'ember-cli-mirage';

const {
  run: { later }
} = Ember;

moduleForComponent('new-version-notifier', 'Integration | Component | new version notifier', {
  integration: true,
  beforeEach() {
    startMirage(this.container);
  },
  afterEach() {
    window.server.shutdown();
  }
});

test('it works', function (assert) {
  assert.expect(12);

  let callCount = 0;

  this.setProperties({
    version:     null,
    lastVersion: null
  });

  window.server.get('/VERSION.txt', function(){
    ++callCount;

    return callCount < 4 ? 'v1.0.' + callCount : 'v1.0.3';
  });

  this.render(hbs`{{new-version-notifier updateInterval=100 version=version lastVersion=lastVersion}}`);

  const done = assert.async(3);

  later(() => {
    assert.equal(this.$().text().trim(), '');
    assert.equal(callCount, 1);
    assert.equal(this.get('version'), 'v1.0.1');
    assert.equal(this.get('lastVersion'), null);

    done();
  }, 0);

  later(() => {
    assert.equal(callCount, 2);
    assert.equal(this.get('version'), 'v1.0.2');
    assert.equal(this.get('lastVersion'), 'v1.0.1');
    assert.equal(this.$().text().trim().replace(/\n|\t/, ''), 'This application has been updated from version v1.0.1 to v1.0.2. Please save any work, then refresh browser to see changes. Reload      ×');

    done();
  }, 150);

  later(() => {
    assert.equal(callCount, 6);
    assert.equal(this.get('version'), 'v1.0.3');
    assert.equal(this.get('lastVersion'), 'v1.0.2');
    assert.equal(this.$().text().trim().replace(/\n|\t/, ''), 'This application has been updated from version v1.0.2 to v1.0.3. Please save any work, then refresh browser to see changes. Reload      ×');

    done();
  }, 650);
});

test('one version', function (assert) {
  assert.expect(4);

  let callCount = 0;

  this.setProperties({
    version:     null,
    lastVersion: null
  });

  window.server.get('/VERSION.txt', function(){
    ++callCount;

    return 'v1.0.3';
  });

  this.render(hbs`{{new-version-notifier updateInterval=100 version=version lastVersion=lastVersion}}`);

  const done = assert.async(1);

  later(() => {
    assert.equal(this.$().text().trim(), '');
    assert.equal(callCount, 4);
    assert.equal(this.get('version'), 'v1.0.3');
    assert.equal(this.get('lastVersion'), null);

    done();
  }, 350);
});

test('repeat on bad response', function (assert) {
  assert.expect(4);

  let callCount = 0;

  this.setProperties({
    version:     null,
    lastVersion: null
  });

  window.server.get('/VERSION.txt', function(){
    ++callCount;

    if (callCount === 1) {
      return new Mirage.Response(500, {}, { message: '' });
    }

    return 'v1.0.3';
  });

  this.render(hbs`{{new-version-notifier updateInterval=100 version=version lastVersion=lastVersion}}`);

  const done = assert.async(1);

  later(() => {
    assert.equal(this.$().text().trim(), '');
    assert.equal(callCount, 4);
    assert.equal(this.get('version'), 'v1.0.3');
    assert.equal(this.get('lastVersion'), null);

    done();
  }, 350);
});<|endoftext|>


<commit_before>import prepaidsApi from 'core/api/prepaids'
import usersApi from 'core/api/users'

const Prepaid = require('models/Prepaid')
const User = require('models/User')

export default {
  namespaced: true,

  state: {
    loading: {
      byTeacher: {}
    },

    prepaids: {
      byTeacher: {} // grouped by status - expired, pending, empty and available
    },

    joiners: { // users in the shared pool of a prepaid
      byPrepaid: {}
    }
  },

  mutations: {
    toggleLoadingForTeacher: (state, teacherId) => {
      Vue.set(
        state.loading.byTeacher,
        teacherId,
        !state.loading.byTeacher[teacherId]
      )
    },

    addPrepaidsForTeacher: (state, { teacherId, prepaids }) => {
      const teacherPrepaids = {
        expired: [],
        pending: [],
        empty: [],
        available: []
      }
      prepaids.forEach((prepaid) => {
        if (prepaid.endDate && new Date(prepaid.endDate) < new Date()) {
          teacherPrepaids.expired.push(prepaid)
        } else if (prepaid.startDate && new Date(prepaid.startDate) > new Date()) {
          teacherPrepaids.pending.push(prepaid)
        } else if (prepaid.maxRedeemers - (prepaid.redeemers || []).length <= 0 || prepaid.maxRedeemers <= 0) {
          teacherPrepaids.empty.push(prepaid)
        } else {
          teacherPrepaids.available.push(prepaid)
        }
      })
      Vue.set(state.prepaids.byTeacher, teacherId, teacherPrepaids)
    },

    setJoinersForPrepaid: (state, { prepaidId, joiners }) => {
      Vue.set(state.joiners.byPrepaid, prepaidId, joiners)
    },

    addJoinerForPrepaid: (state, { prepaidId, joiner }) => {
      const joiners = state.joiners.byPrepaid[prepaidId] || []
      joiners.push({
        _id: joiner._id,
        firstName: joiner.firstName,
        lastName: joiner.lastName,
        email: joiner.email
      })
      Vue.set(state.joiners.byPrepaid, prepaidId, joiners)
    }
  },

  getters: {
    getPrepaidsByTeacher: (state) => (id) => {
      return state.prepaids.byTeacher[id]
    },

    getLicensesStatsByTeacher: (_state, getters) => (id) => {
      const prepaids = getters.getPrepaidsByTeacher(id)
      if (!prepaids) {
        return { totalAvailableSpots: 0, totalSpots: 0 }
      }
      const availablePrepaids = prepaids.available.map(data => new Prepaid(data))
      const totalAvailableSpots = availablePrepaids.reduce((acc, prepaid) => acc + prepaid.openSpots(), 0)
      const totalSpots = availablePrepaids.reduce((acc, prepaid) => acc + prepaid.totalSpots(), 0)
      return { totalAvailableSpots, totalSpots, usedLicenses: totalSpots - totalAvailableSpots }
    },

    getActiveLicensesForTeacher: (_state, getters) => (id) => {
      const prepaids = getters.getPrepaidsByTeacher(id)
      if (!prepaids) {
        return []
      }
      let active = []
      active = active.concat(prepaids.available).concat(prepaids.empty).concat(prepaids.pending)
      return active
    },

    getExpiredLicensesForTeacher: (_state, getters) => (id) => {
      const prepaids = getters.getPrepaidsByTeacher(id)
      if (!prepaids) {
        return []
      }
      return prepaids.expired
    },

    getJoinersForPrepaid: (state) => (id) => {
      return state.joiners.byPrepaid[id] || []
    }
  },

  actions: {
    fetchPrepaidsForTeacher: ({ commit }, teacherId) => {
      commit('toggleLoadingForTeacher', teacherId)

      // Fetch teacher's prepaids and shared prepaids.
      return prepaidsApi.getByCreator(teacherId, { data: { includeShared: true } })
        .then(res => {
          if (res) {
            commit('addPrepaidsForTeacher', {
              teacherId,
              prepaids: res
            })
          } else {
            throw new Error('Unexpected response from fetch classrooms API.')
          }
        })
        .catch((e) => noty({ text: 'Fetch prepaids failure' + e, type: 'error', layout: 'topCenter', timeout: 2000 }))
        .finally(() => commit('toggleLoadingForTeacher', teacherId))
    },

    fetchJoinersForPrepaid: ({ commit }, prepaidId) => {
      return prepaidsApi.fetchJoiners({ prepaidID: prepaidId })
        .then(joiners => {
          if (joiners) {
            commit('setJoinersForPrepaid', {
              prepaidId,
              joiners
            })
          } else {
            throw new Error('Unexpected response from fetch joiners API.')
          }
        })
        .catch((e) => console.error('Error in fetching prepaid joiners:', e))
    },

    addJoinerForPrepaid: ({ commit }, { prepaidId, email }) => {
      return usersApi.getByEmail({ email })
        .then(user => {
          if (user) {
            prepaidsApi.addJoiner({ prepaidID: prepaidId, userID: user._id })
              .then(() => {
                commit('addJoinerForPrepaid', { prepaidId: prepaidId, joiner: user })
              })
              .catch((e) => noty({ text: 'Error:' + e.message, type: 'error', layout: 'topCenter', timeout: 2000 }))
          }
        })
        .catch((e) => console.error('Error in adding prepaid joiner:', e))
    },

    async applyLicenses ({ getters }, { members, teacherId }) {
      const prepaids = getters.getPrepaidsByTeacher(teacherId)
      if (!prepaids) {
        throw new Error(`no prepaids for the teacher`)
      }

      const students = members.map(data => new User(data))
      const availablePrepaids = prepaids.available.map(data => new Prepaid(data))

      const unenrolledStudents = students
        .filter(user => !user.isEnrolled())

      const { totalAvailableSpots } = getters.getLicensesStatsByTeacher(teacherId)

      const canApplyLicenses = totalAvailableSpots >= unenrolledStudents.length
      const additionalLicensesNum = unenrolledStudents.length - totalAvailableSpots

      if (!canApplyLicenses) {
        // NOTE: Should we have specific UI noty side effects within the store logic?
        noty({
          text: `Oops! It looks like you need ${additionalLicensesNum} more license${additionalLicensesNum > 1 ? 's' : ''}. Visit My Licenses to learn more!`,
          layout: 'center',
          type: 'error',
          killer: true,
          timeout: 5000
        })
        return
      }
      const numberEnrolled = unenrolledStudents.length
      if (numberEnrolled) {
        let confirmed = false
        // NOTE: Should we have specific UI noty side effects within the store logic?
        await new Promise((resolve) => noty({
          text: `Please confirm that you'd like to apply licenses to ${numberEnrolled} student(s). You will have ${totalAvailableSpots - unenrolledStudents.length} license(s) remaining.`,
          type: 'info',
          buttons: [
            {
              addClass: 'btn btn-primary',
              text: 'Ok',
              onClick: function ($noty) {
                confirmed = true
                $noty.close()
                resolve()
              }
            },
            {
              addClass: 'btn btn-danger',
              text: 'Cancel',
              onClick: function ($noty) {
                $noty.close()
                resolve()
              }
            }
          ]
        }))

        if (!confirmed) {
          return
        }
      }

      const requests = []

      for (const prepaid of availablePrepaids) {
        if (unenrolledStudents.length === 0) {
          // Finished enrolling students.
          break
        }
        if (!Math.min(unenrolledStudents.length, prepaid.openSpots()) > 0) {
          // Not able to assign to this prepaid.
          continue
        }

        const availableLicenses = Math.min(unenrolledStudents.length, prepaid.openSpots())
        for (let i = 0; i < availableLicenses; i++) {
          const user = unenrolledStudents.pop()
          requests.push(prepaid.redeem(user.get('_id')))
        }
      }

      // TODO: Handle error
      await Promise.all(requests)
    },

    async revokeLicenses (_, { members }) {
      const students = members.map(data => new User(data)).filter(u => u.isEnrolled())

      const existsLicenseToRevoke = students.length > 0
      if (!existsLicenseToRevoke) {
        noty({ text: `No licenses applied to selected student(s).` })
        return
      }

      let confirmed = false
      await new Promise((resolve) => noty({
        text: `Revoking a license will make it available to apply to other students. Students will no longer be able to access paid content, but their progress will be saved. Please confirm you'd like to proceed.`,
        buttons: [
          {
            addClass: 'btn btn-primary',
            text: 'Ok',
            onClick: function ($noty) {
              confirmed = true
              $noty.close()
              resolve()
            }
          },
          {
            addClass: 'btn btn-danger',
            text: 'Cancel',
            onClick: function ($noty) {
              $noty.close()
              resolve()
            }
          }
        ]
      }))

      if (!confirmed) {
        return
      }

      const promises = []
      for (const student of students) {
        const prepaid = student.makeCoursePrepaid()
        promises.push(new Promise((resolve, reject) =>
          prepaid.revoke(student, {
            success: resolve,
            error: reject
          })
        ))
      }

      // TODO: Handle error
      await Promise.all(promises)
    }
  }
}
<commit_msg>Bug TD: Swallow license revoke error to prevent UI freeze (#530)<commit_after>import prepaidsApi from 'core/api/prepaids'
import usersApi from 'core/api/users'

const Prepaid = require('models/Prepaid')
const User = require('models/User')

export default {
  namespaced: true,

  state: {
    loading: {
      byTeacher: {}
    },

    prepaids: {
      byTeacher: {} // grouped by status - expired, pending, empty and available
    },

    joiners: { // users in the shared pool of a prepaid
      byPrepaid: {}
    }
  },

  mutations: {
    toggleLoadingForTeacher: (state, teacherId) => {
      Vue.set(
        state.loading.byTeacher,
        teacherId,
        !state.loading.byTeacher[teacherId]
      )
    },

    addPrepaidsForTeacher: (state, { teacherId, prepaids }) => {
      const teacherPrepaids = {
        expired: [],
        pending: [],
        empty: [],
        available: []
      }
      prepaids.forEach((prepaid) => {
        if (prepaid.endDate && new Date(prepaid.endDate) < new Date()) {
          teacherPrepaids.expired.push(prepaid)
        } else if (prepaid.startDate && new Date(prepaid.startDate) > new Date()) {
          teacherPrepaids.pending.push(prepaid)
        } else if (prepaid.maxRedeemers - (prepaid.redeemers || []).length <= 0 || prepaid.maxRedeemers <= 0) {
          teacherPrepaids.empty.push(prepaid)
        } else {
          teacherPrepaids.available.push(prepaid)
        }
      })
      Vue.set(state.prepaids.byTeacher, teacherId, teacherPrepaids)
    },

    setJoinersForPrepaid: (state, { prepaidId, joiners }) => {
      Vue.set(state.joiners.byPrepaid, prepaidId, joiners)
    },

    addJoinerForPrepaid: (state, { prepaidId, joiner }) => {
      const joiners = state.joiners.byPrepaid[prepaidId] || []
      joiners.push({
        _id: joiner._id,
        firstName: joiner.firstName,
        lastName: joiner.lastName,
        email: joiner.email
      })
      Vue.set(state.joiners.byPrepaid, prepaidId, joiners)
    }
  },

  getters: {
    getPrepaidsByTeacher: (state) => (id) => {
      return state.prepaids.byTeacher[id]
    },

    getLicensesStatsByTeacher: (_state, getters) => (id) => {
      const prepaids = getters.getPrepaidsByTeacher(id)
      if (!prepaids) {
        return { totalAvailableSpots: 0, totalSpots: 0 }
      }
      const availablePrepaids = prepaids.available.map(data => new Prepaid(data))
      const totalAvailableSpots = availablePrepaids.reduce((acc, prepaid) => acc + prepaid.openSpots(), 0)
      const totalSpots = availablePrepaids.reduce((acc, prepaid) => acc + prepaid.totalSpots(), 0)
      return { totalAvailableSpots, totalSpots, usedLicenses: totalSpots - totalAvailableSpots }
    },

    getActiveLicensesForTeacher: (_state, getters) => (id) => {
      const prepaids = getters.getPrepaidsByTeacher(id)
      if (!prepaids) {
        return []
      }
      let active = []
      active = active.concat(prepaids.available).concat(prepaids.empty).concat(prepaids.pending)
      return active
    },

    getExpiredLicensesForTeacher: (_state, getters) => (id) => {
      const prepaids = getters.getPrepaidsByTeacher(id)
      if (!prepaids) {
        return []
      }
      return prepaids.expired
    },

    getJoinersForPrepaid: (state) => (id) => {
      return state.joiners.byPrepaid[id] || []
    }
  },

  actions: {
    fetchPrepaidsForTeacher: ({ commit }, teacherId) => {
      commit('toggleLoadingForTeacher', teacherId)

      // Fetch teacher's prepaids and shared prepaids.
      return prepaidsApi.getByCreator(teacherId, { data: { includeShared: true } })
        .then(res => {
          if (res) {
            commit('addPrepaidsForTeacher', {
              teacherId,
              prepaids: res
            })
          } else {
            throw new Error('Unexpected response from fetch classrooms API.')
          }
        })
        .catch((e) => noty({ text: 'Fetch prepaids failure' + e, type: 'error', layout: 'topCenter', timeout: 2000 }))
        .finally(() => commit('toggleLoadingForTeacher', teacherId))
    },

    fetchJoinersForPrepaid: ({ commit }, prepaidId) => {
      return prepaidsApi.fetchJoiners({ prepaidID: prepaidId })
        .then(joiners => {
          if (joiners) {
            commit('setJoinersForPrepaid', {
              prepaidId,
              joiners
            })
          } else {
            throw new Error('Unexpected response from fetch joiners API.')
          }
        })
        .catch((e) => console.error('Error in fetching prepaid joiners:', e))
    },

    addJoinerForPrepaid: ({ commit }, { prepaidId, email }) => {
      return usersApi.getByEmail({ email })
        .then(user => {
          if (user) {
            prepaidsApi.addJoiner({ prepaidID: prepaidId, userID: user._id })
              .then(() => {
                commit('addJoinerForPrepaid', { prepaidId: prepaidId, joiner: user })
              })
              .catch((e) => noty({ text: 'Error:' + e.message, type: 'error', layout: 'topCenter', timeout: 2000 }))
          }
        })
        .catch((e) => console.error('Error in adding prepaid joiner:', e))
    },

    async applyLicenses ({ getters }, { members, teacherId }) {
      const prepaids = getters.getPrepaidsByTeacher(teacherId)
      if (!prepaids) {
        throw new Error(`no prepaids for the teacher`)
      }

      const students = members.map(data => new User(data))
      const availablePrepaids = prepaids.available.map(data => new Prepaid(data))

      const unenrolledStudents = students
        .filter(user => !user.isEnrolled())

      const { totalAvailableSpots } = getters.getLicensesStatsByTeacher(teacherId)

      const canApplyLicenses = totalAvailableSpots >= unenrolledStudents.length
      const additionalLicensesNum = unenrolledStudents.length - totalAvailableSpots

      if (!canApplyLicenses) {
        // NOTE: Should we have specific UI noty side effects within the store logic?
        noty({
          text: `Oops! It looks like you need ${additionalLicensesNum} more license${additionalLicensesNum > 1 ? 's' : ''}. Visit My Licenses to learn more!`,
          layout: 'center',
          type: 'error',
          killer: true,
          timeout: 5000
        })
        return
      }
      const numberEnrolled = unenrolledStudents.length
      if (numberEnrolled) {
        let confirmed = false
        // NOTE: Should we have specific UI noty side effects within the store logic?
        await new Promise((resolve) => noty({
          text: `Please confirm that you'd like to apply licenses to ${numberEnrolled} student(s). You will have ${totalAvailableSpots - unenrolledStudents.length} license(s) remaining.`,
          type: 'info',
          buttons: [
            {
              addClass: 'btn btn-primary',
              text: 'Ok',
              onClick: function ($noty) {
                confirmed = true
                $noty.close()
                resolve()
              }
            },
            {
              addClass: 'btn btn-danger',
              text: 'Cancel',
              onClick: function ($noty) {
                $noty.close()
                resolve()
              }
            }
          ]
        }))

        if (!confirmed) {
          return
        }
      }

      const requests = []

      for (const prepaid of availablePrepaids) {
        if (unenrolledStudents.length === 0) {
          // Finished enrolling students.
          break
        }
        if (!Math.min(unenrolledStudents.length, prepaid.openSpots()) > 0) {
          // Not able to assign to this prepaid.
          continue
        }

        const availableLicenses = Math.min(unenrolledStudents.length, prepaid.openSpots())
        for (let i = 0; i < availableLicenses; i++) {
          const user = unenrolledStudents.pop()
          requests.push(prepaid.redeem(user.get('_id')))
        }
      }

      // TODO: Handle error
      await Promise.all(requests)
    },

    async revokeLicenses (_, { members }) {
      const students = members.map(data => new User(data)).filter(u => u.isEnrolled())

      const existsLicenseToRevoke = students.length > 0
      if (!existsLicenseToRevoke) {
        noty({ text: `No licenses applied to selected student(s).` })
        return
      }

      let confirmed = false
      await new Promise((resolve) => noty({
        text: `Revoking a license will make it available to apply to other students. Students will no longer be able to access paid content, but their progress will be saved. Please confirm you'd like to proceed.`,
        buttons: [
          {
            addClass: 'btn btn-primary',
            text: 'Ok',
            onClick: function ($noty) {
              confirmed = true
              $noty.close()
              resolve()
            }
          },
          {
            addClass: 'btn btn-danger',
            text: 'Cancel',
            onClick: function ($noty) {
              $noty.close()
              resolve()
            }
          }
        ]
      }))

      if (!confirmed) {
        return
      }

      const promises = []
      for (const student of students) {
        const prepaid = student.makeCoursePrepaid()
        promises.push(new Promise((resolve, reject) =>
          prepaid.revoke(student, {
            success: resolve,
            error: () => {
              console.error(`Didn't revoke this license`)
              resolve()
            }
          })
        ))
      }

      // TODO: Handle error
      await Promise.all(promises)
    }
  }
}
<|endoftext|>


<commit_before>// Part of <http://miracle.systems/p/h5.modbus> licensed under <MIT>

'use strict';

const inspect = require('util').inspect;
const helpers = require('../helpers');
const FunctionCode = require('../FunctionCode');
const Request = require('../Request');

/**
 * The write multiple registers request (code 0x10).
 *
 * A binary representation of this request varies in length and consists of:
 *
 *   - a function code (1 byte),
 *   - a starting address (2 bytes),
 *   - a quantity of registers (2 bytes),
 *   - a byte count (`n`; 1 byte),
 *   - new register data (`n` bytes).
 */
class WriteMultipleRegistersRequest extends Request
{
  /**
   * @param {number} startingAddress A starting address of the registers to write. Must be between 0 and 0xFFFF.
   * @param {Buffer} values Register values to write. Must have an even length of between 2 and 246 bytes.
   * @throws {Error} If the specified `startingAddress` is not a number between 0 and 0xFFFF.
   * @throws {Error} If the specified `values` is not an array of length between 1 and 1968.
   * @throws {Error} If the sum of `startingAddress` and a half of `values.length` is not between 1 and 0x10000.
   */
  constructor(startingAddress, values)
  {
    super(FunctionCode.WriteMultipleCoils);

    /**
     * Register values to write. A Buffer of even length between 2 and 246 bytes.
     *
     * @readonly
     * @type {Buffer}
     */
    this.values = helpers.prepareRegisterValues(values);

    /**
     * A starting address of the registers to write. A number between 0 and 0xFFFF.
     *
     * @readonly
     * @type {number}
     */
    this.startingAddress = helpers.prepareAddress(startingAddress, this.quantity);
  }

  /**
   * Creates a new request from the specified `options`.
   *
   * @param {Object} options
   * @param {number} [options.startingAddress=0] A starting address of the outputs to set.
   * If specified, must be a number between 0 and 0xFFFF. Defaults to 0.
   * @param {Buffer} options.values Register values to write.
   * Must be a Buffer of even length between 2 and 246 bytes.
   * @returns {WriteMultipleRegistersRequest}
   * @throws {Error} If any of the specified `options` are invalid.
   */
  static fromOptions(options)
  {
    return new WriteMultipleRegistersRequest(options.startingAddress, options.values);
  }

  /**
   * Creates a new request from its binary representation.
   *
   * @param {Buffer} buffer A binary representation of this request.
   * @returns {WriteMultipleRegistersRequest}
   * @throws {Error} If the specified `buffer` is not a valid binary representation of this request.
   */
  static fromBuffer(buffer)
  {
    helpers.assertBufferLength(buffer, 6 + (buffer[5] || 2));
    helpers.assertFunctionCode(buffer[0], FunctionCode.WriteMultipleRegisters);

    const startingAddress = buffer.readUInt16BE(1, true);
    const byteCount = buffer[5]; // 3 & 4 - quantity
    const values = buffer.slice(6, 6 + byteCount);

    return new WriteMultipleRegistersRequest(startingAddress, values);
  }

  /**
   * An ending address. A number between 1 and 0x10000.
   *
   * @type {number}
   */
  get endingAddress()
  {
    return this.startingAddress + this.quantity;
  }

  /**
   * A quantity of registers to write. An even number between 1 to 123.
   *
   * @type {number}
   */
  get quantity()
  {
    return this.values.length / 2;
  }

  /**
   * Returns a binary representation of this request.
   *
   * @returns {Buffer}
   */
  toBuffer()
  {
    var buffer = new Buffer(6 + this.values.length);

    buffer[0] = this.functionCode;
    buffer.writeUInt16BE(this.startingAddress, 1, true);
    buffer.writeUInt16BE(this.quantity, 3, true);
    buffer[5] = this.values.length;
    this.values.copy(buffer, 6);

    return buffer;
  }

  /**
   * Returns a string representation of this request.
   *
   * @returns {string}
   */
  toString()
  {
    if (this.quantity === 1)
    {
      return helpers.formatRequest(
        this, `Set register at address ${this.startingAddress} to ${this.values.readUInt16BE(0)}.`
      );
    }

    return helpers.formatRequest(
      this, `Set ${this.quantity} registers starting at address ${this.startingAddress} to: ${inspect(this.values)}`
    );
  }
}

module.exports = WriteMultipleRegistersRequest;
<commit_msg>WriteMultipleRegistersRequest: fixed an invalid function code.<commit_after>// Part of <http://miracle.systems/p/h5.modbus> licensed under <MIT>

'use strict';

const inspect = require('util').inspect;
const helpers = require('../helpers');
const FunctionCode = require('../FunctionCode');
const Request = require('../Request');

/**
 * The write multiple registers request (code 0x10).
 *
 * A binary representation of this request varies in length and consists of:
 *
 *   - a function code (1 byte),
 *   - a starting address (2 bytes),
 *   - a quantity of registers (2 bytes),
 *   - a byte count (`n`; 1 byte),
 *   - new register data (`n` bytes).
 */
class WriteMultipleRegistersRequest extends Request
{
  /**
   * @param {number} startingAddress A starting address of the registers to write. Must be between 0 and 0xFFFF.
   * @param {Buffer} values Register values to write. Must have an even length of between 2 and 246 bytes.
   * @throws {Error} If the specified `startingAddress` is not a number between 0 and 0xFFFF.
   * @throws {Error} If the specified `values` is not an array of length between 1 and 1968.
   * @throws {Error} If the sum of `startingAddress` and a half of `values.length` is not between 1 and 0x10000.
   */
  constructor(startingAddress, values)
  {
    super(FunctionCode.WriteMultipleRegisters);

    /**
     * Register values to write. A Buffer of even length between 2 and 246 bytes.
     *
     * @readonly
     * @type {Buffer}
     */
    this.values = helpers.prepareRegisterValues(values);

    /**
     * A starting address of the registers to write. A number between 0 and 0xFFFF.
     *
     * @readonly
     * @type {number}
     */
    this.startingAddress = helpers.prepareAddress(startingAddress, this.quantity);
  }

  /**
   * Creates a new request from the specified `options`.
   *
   * @param {Object} options
   * @param {number} [options.startingAddress=0] A starting address of the outputs to set.
   * If specified, must be a number between 0 and 0xFFFF. Defaults to 0.
   * @param {Buffer} options.values Register values to write.
   * Must be a Buffer of even length between 2 and 246 bytes.
   * @returns {WriteMultipleRegistersRequest}
   * @throws {Error} If any of the specified `options` are invalid.
   */
  static fromOptions(options)
  {
    return new WriteMultipleRegistersRequest(options.startingAddress, options.values);
  }

  /**
   * Creates a new request from its binary representation.
   *
   * @param {Buffer} buffer A binary representation of this request.
   * @returns {WriteMultipleRegistersRequest}
   * @throws {Error} If the specified `buffer` is not a valid binary representation of this request.
   */
  static fromBuffer(buffer)
  {
    helpers.assertBufferLength(buffer, 6 + (buffer[5] || 2));
    helpers.assertFunctionCode(buffer[0], FunctionCode.WriteMultipleRegisters);

    const startingAddress = buffer.readUInt16BE(1, true);
    const byteCount = buffer[5]; // 3 & 4 - quantity
    const values = buffer.slice(6, 6 + byteCount);

    return new WriteMultipleRegistersRequest(startingAddress, values);
  }

  /**
   * An ending address. A number between 1 and 0x10000.
   *
   * @type {number}
   */
  get endingAddress()
  {
    return this.startingAddress + this.quantity;
  }

  /**
   * A quantity of registers to write. An even number between 1 to 123.
   *
   * @type {number}
   */
  get quantity()
  {
    return this.values.length / 2;
  }

  /**
   * Returns a binary representation of this request.
   *
   * @returns {Buffer}
   */
  toBuffer()
  {
    var buffer = new Buffer(6 + this.values.length);

    buffer[0] = this.functionCode;
    buffer.writeUInt16BE(this.startingAddress, 1, true);
    buffer.writeUInt16BE(this.quantity, 3, true);
    buffer[5] = this.values.length;
    this.values.copy(buffer, 6);

    return buffer;
  }

  /**
   * Returns a string representation of this request.
   *
   * @returns {string}
   */
  toString()
  {
    if (this.quantity === 1)
    {
      return helpers.formatRequest(
        this, `Set register at address ${this.startingAddress} to ${this.values.readUInt16BE(0)}.`
      );
    }

    return helpers.formatRequest(
      this, `Set ${this.quantity} registers starting at address ${this.startingAddress} to: ${inspect(this.values)}`
    );
  }
}

module.exports = WriteMultipleRegistersRequest;
<|endoftext|>


<commit_before>'use strict';

angular.module('ddsCommon').constant('ressourceTypes', [
    {
        id: 'revenusSalarie',
        label: 'Salaires, primes',
        category: 'revenusActivite'
    },
    {
        id: 'stage',
        label: 'Rémunération de stage',
        category: 'revenusActivite'
    },
    {
        id: 'revenusStageFormationPro',
        label: 'Revenus de stage de formation professionnelle',
        category: 'revenusActivite'
    },
    {
        id: 'allocationsChomage',
        label: 'Allocation chômage (ARE)',
        category: 'allocations'
    },
    {
        id: 'allocationSecurisationPro',
        label: 'Allocation de sécurisation professionnelle',
        category: 'allocations'
    },
    {
        id: 'primeRepriseActivite',
        label: 'Prime forfaitaire mensuelle pour la reprise d’activité',
        category: 'allocations'
    },
    {
        id: 'allocationLogement',
        label: 'Aide au logement',
        category: 'allocations'
    },
    {
        id: 'rsa',
        label: 'Revenu de solidarité active (RSA)',
        category: 'allocations'
    },
    {
        id: 'asf',
        label: 'Allocation de soutien familial (ASF)',
        category: 'allocations'
    },
    {
        id: 'aspa',
        label: 'Allocation de solidarité aux personnes âgées (ASPA)',
        category: 'allocations'
    },
    {
        id: 'ass',
        label: 'Allocation de solidarité spécifique (ASS)',
        category: 'allocations'
    },
    {
        id: 'aah',
        label: 'Allocation adulte handicapé (AAH)',
        category: 'allocations'
    },
    {
        id: 'indJourMaternite',
        label: 'Indemnités de maternité, paternité, adoption',
        category: 'indemnites'
    },
    {
        id: 'indJourMaladie',
        label: 'Indemnités maladie',
        category: 'indemnites'
    },
    {
        id: 'indJourMaladieProf',
        label: 'Indemnités maladie professionnelle',
        category: 'indemnites'
    },
    {
        id: 'indJourAccidentDuTravail',
        label: 'Indemnités d’accident du travail',
        category: 'indemnites'
    },
    {
        id: 'indChomagePartiel',
        label: 'Indemnités de chômage ou d\'activité partiel(le)',
        category: 'indemnites'
    },
    {
        id: 'indVolontariat',
        label: 'Indemnités de volontariat',
        category: 'indemnites'
    },
    {
        id: 'dedommagementAmiante',
        label: 'Dédommagement aux victimes de l’amiante',
        category: 'indemnites'
    },
    {
        id: 'pensionsAlimentaires',
        label: 'Pensions alimentaires',
        category: 'pensions'
    },
    {
        id: 'prestationCompensatoire',
        label: 'Prestation compensatoire (suite à séparation)',
        category: 'pensions'
    },
    {
        id: 'pensionsRetraitesRentes',
        label: 'Retraite (y compris reversion), rentes',
        category: 'pensions'
    },
    {
        id: 'retraiteCombattant',
        label: 'Retraite du combattant',
        category: 'pensions'
    },
    {
        id: 'pensionsInvalidite',
        label: 'Pensions d\'invalidité',
        category: 'pensions'
    },
    {
        id: 'bourseEnseignementSup',
        label: 'Bourses de l\'enseignement supérieur',
        category: 'autre'
    },
    {
        id: 'bourseRecherche',
        label: 'Bourse de recherche',
        category: 'autre'
    },
    {
        id: 'gainsExceptionnels',
        label: 'Gains exceptionnels (dons, gains aux jeux, héritage)',
        category: 'autre'
    },
    {
        id: 'caMicroEntreprise',
        label: 'Micro-entreprise / auto-entrepreneur',
        category: 'tns'
    },
    {
        id: 'autresRevenusTns',
        label: 'Profession libérale, entrepreneur',
        category: 'tns'
    }
]);
<commit_msg>Retire les prestations simulées de la liste des ressources<commit_after>'use strict';

angular.module('ddsCommon').constant('ressourceTypes', [
    {
        id: 'revenusSalarie',
        label: 'Salaires, primes',
        category: 'revenusActivite'
    },
    {
        id: 'stage',
        label: 'Rémunération de stage',
        category: 'revenusActivite'
    },
    {
        id: 'revenusStageFormationPro',
        label: 'Revenus de stage de formation professionnelle',
        category: 'revenusActivite'
    },
    {
        id: 'allocationsChomage',
        label: 'Allocation chômage (ARE)',
        category: 'allocations'
    },
    {
        id: 'allocationSecurisationPro',
        label: 'Allocation de sécurisation professionnelle',
        category: 'allocations'
    },
    {
        id: 'primeRepriseActivite',
        label: 'Prime forfaitaire mensuelle pour la reprise d’activité',
        category: 'allocations'
    },
    // {
    //     id: 'allocationLogement',
    //     label: 'Aide au logement',
    //     category: 'allocations'
    // },
    // {
    //     id: 'rsa',
    //     label: 'Revenu de solidarité active (RSA)',
    //     category: 'allocations'
    // },
    // {
    //     id: 'asf',
    //     label: 'Allocation de soutien familial (ASF)',
    //     category: 'allocations'
    // },
    // {
    //     id: 'aspa',
    //     label: 'Allocation de solidarité aux personnes âgées (ASPA)',
    //     category: 'allocations'
    // },
    // {
    //     id: 'ass',
    //     label: 'Allocation de solidarité spécifique (ASS)',
    //     category: 'allocations'
    // },
    {
        id: 'aah',
        label: 'Allocation adulte handicapé (AAH)',
        category: 'allocations'
    },
    {
        id: 'indJourMaternite',
        label: 'Indemnités de maternité, paternité, adoption',
        category: 'indemnites'
    },
    {
        id: 'indJourMaladie',
        label: 'Indemnités maladie',
        category: 'indemnites'
    },
    {
        id: 'indJourMaladieProf',
        label: 'Indemnités maladie professionnelle',
        category: 'indemnites'
    },
    {
        id: 'indJourAccidentDuTravail',
        label: 'Indemnités d’accident du travail',
        category: 'indemnites'
    },
    {
        id: 'indChomagePartiel',
        label: 'Indemnités de chômage ou d\'activité partiel(le)',
        category: 'indemnites'
    },
    {
        id: 'indVolontariat',
        label: 'Indemnités de volontariat',
        category: 'indemnites'
    },
    {
        id: 'dedommagementAmiante',
        label: 'Dédommagement aux victimes de l’amiante',
        category: 'indemnites'
    },
    {
        id: 'pensionsAlimentaires',
        label: 'Pensions alimentaires',
        category: 'pensions'
    },
    {
        id: 'prestationCompensatoire',
        label: 'Prestation compensatoire (suite à séparation)',
        category: 'pensions'
    },
    {
        id: 'pensionsRetraitesRentes',
        label: 'Retraite (y compris reversion), rentes',
        category: 'pensions'
    },
    {
        id: 'retraiteCombattant',
        label: 'Retraite du combattant',
        category: 'pensions'
    },
    {
        id: 'pensionsInvalidite',
        label: 'Pensions d\'invalidité',
        category: 'pensions'
    },
    {
        id: 'bourseEnseignementSup',
        label: 'Bourses de l\'enseignement supérieur',
        category: 'autre'
    },
    {
        id: 'bourseRecherche',
        label: 'Bourse de recherche',
        category: 'autre'
    },
    {
        id: 'gainsExceptionnels',
        label: 'Gains exceptionnels (dons, gains aux jeux, héritage)',
        category: 'autre'
    },
    {
        id: 'caMicroEntreprise',
        label: 'Micro-entreprise / auto-entrepreneur',
        category: 'tns'
    },
    {
        id: 'autresRevenusTns',
        label: 'Profession libérale, entrepreneur',
        category: 'tns'
    }
]);
<|endoftext|>


<commit_before>import { getThrottleLazyload, watchAppear } from '../utils'

const supportedEvents = [
  'click', 'longpress', 'appear', 'disappear'
  // 'touchstart', 'touchmove', 'touchend'
]

const scrollableTypes = ['scroller', 'list']

let lazyloadWatched = false
function watchLazyload () {
  lazyloadWatched = true
  ; [
    'scroll'
    // 'transitionend',
    // 'webkitTransitionEnd',
    // 'animationend',
    // 'webkitAnimationEnd',
    // 'resize'
  ].forEach(evt => {
    window.addEventListener(evt, getThrottleLazyload(25, document.body))
  })
}

export default {
  beforeCreate () {
    if (!lazyloadWatched) {
      watchLazyload()
    }
  },

  mounted () {
    if (!weex._root) {
      weex._root = this.$root.$el
      weex._root.classList.add('weex-root')
    }
    watchAppear(this)
  },

  methods: {
    _getTopContext () {
      let ctx = this
      let vnode = ctx.$options._parentVnode
      while (vnode) {
        ctx = vnode.context
        vnode = ctx.$options._parentVnode
      }
      return ctx
    },

    _getScopeId () {
      // return closest scopeId.
      let scopeId = this.$options._scopeId
      let ctx = this
      while (!scopeId) {
        ctx = ctx.$options.parent
        if (!ctx) return null
        scopeId = ctx.$options._scopeId
      }
      return scopeId
    },

    _getParentScroller () {
      let parent = this
      while (parent && scrollableTypes.indexOf(parent.$options._componentTag) <= -1) {
        parent = parent.$options.parent
      }
      return parent
    },

    _createEventMap (extras = []) {
      const eventMap = {}
      supportedEvents.concat(extras).forEach(name => {
        eventMap[name] = event => this.$emit(name, event)
      })
      return eventMap
    },

    _fireLazyload (el) {
      getThrottleLazyload(16)()
    }
  }
}
<commit_msg>* [html5] freeze weex.<commit_after>import { getThrottleLazyload, watchAppear } from '../utils'

const supportedEvents = [
  'click', 'longpress', 'appear', 'disappear'
  // 'touchstart', 'touchmove', 'touchend'
]

const scrollableTypes = ['scroller', 'list']

let lazyloadWatched = false
function watchLazyload () {
  lazyloadWatched = true
  ; [
    'scroll'
    // 'transitionend',
    // 'webkitTransitionEnd',
    // 'animationend',
    // 'webkitAnimationEnd',
    // 'resize'
  ].forEach(evt => {
    window.addEventListener(evt, getThrottleLazyload(25, document.body))
  })
}

export default {
  beforeCreate () {
    if (!lazyloadWatched) {
      watchLazyload()
    }
  },

  mounted () {
    if (!weex._root) {
      weex._root = this.$root.$el
      weex._root.classList.add('weex-root')
      Object.freeze(weex)
    }
    watchAppear(this)
  },

  methods: {
    _getTopContext () {
      let ctx = this
      let vnode = ctx.$options._parentVnode
      while (vnode) {
        ctx = vnode.context
        vnode = ctx.$options._parentVnode
      }
      return ctx
    },

    _getScopeId () {
      // return closest scopeId.
      let scopeId = this.$options._scopeId
      let ctx = this
      while (!scopeId) {
        ctx = ctx.$options.parent
        if (!ctx) return null
        scopeId = ctx.$options._scopeId
      }
      return scopeId
    },

    _getParentScroller () {
      let parent = this
      while (parent && scrollableTypes.indexOf(parent.$options._componentTag) <= -1) {
        parent = parent.$options.parent
      }
      return parent
    },

    _createEventMap (extras = []) {
      const eventMap = {}
      supportedEvents.concat(extras).forEach(name => {
        eventMap[name] = event => this.$emit(name, event)
      })
      return eventMap
    },

    _fireLazyload (el) {
      getThrottleLazyload(16)()
    }
  }
}
<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter;
var util = require('util');
var utils = require('../../util/utils');
var wsClient = require('ws');
var zlib = require('zlib');
var Tracer = require('../../util/tracer');
var logger = require('pomelo-logger').getLogger('pomelo-rpc', __filename);
var DEFAULT_CALLBACK_TIMEOUT = 15 * 1000;
var DEFAULT_INTERVAL = 50;

var KEEP_ALIVE_TIMEOUT = 10 * 1000;
var KEEP_ALIVE_INTERVAL = 30 * 1000;

var DEFAULT_ZIP_LENGTH = 1024 * 10;
var useZipCompress = false;

var MailBox = function(server, opts) {
  EventEmitter.call(this);
  this.id = server.id;
  this.host = server.host;
  this.port = server.port;
  this.requests = {};
  this.timeout = {};
  this.curId = 0;
  this.queue = [];
  this.bufferMsg = opts.bufferMsg;
  this.interval = opts.interval || DEFAULT_INTERVAL;
  this.timeoutValue = opts.timeout || DEFAULT_CALLBACK_TIMEOUT;
  this.connected = false;
  this.closed = false;
  this.opts = opts;
  this._KPinterval = null;
  this._KP_last_ping_time = -1;
  this._KP_last_pong_time = -1;
  DEFAULT_ZIP_LENGTH = opts.doZipLength || DEFAULT_ZIP_LENGTH;
  useZipCompress = opts.useZipCompress || false;
};
util.inherits(MailBox, EventEmitter);

var  pro = MailBox.prototype;

pro.connect = function(tracer, cb) {
  tracer.info('client', __filename, 'connect', 'ws-mailbox try to connect');
  if(this.connected) {
    tracer.error('client', __filename, 'connect', 'mailbox has already connected');
    utils.invokeCallback(cb, new Error('mailbox has already connected.'));
    return;
  }

  this.socket = wsClient.connect('ws://' + this.host + ':' + this.port);
  //this.socket = wsClient.connect(this.host + ':' + this.port, {'force new connection': true, 'reconnect': false});

  var self = this;
  this.socket.on('message', function(data, flags) {
    try {
      if (flags.binary) {
        console.log('ws rpc received message flags.binary, len = ', data.length);
        zlib.gunzip(data, function (err, result) {
          if (!!err) {
            console.warn('ws rpc received binary message error: %j', err.stack);
            return;
          }
          //console.log("ws rpc client received message unzip len = " + result.length);
          console.log("websocket_mailbox_recv_zip: " + result.substring(0, 300));
          var msg = {};
          if (result.length > 0 && result[result.length - 1] != "}") {
            console.warn("websocket_mailbox_recv_zip: binary last word is not } ");
            result = result.substring(0, result.length - 1);
          }
          msg = JSON.parse(result);
          if (Array.isArray(msg.body)) {
            processMsgs(self, msg.body);
          } else {
            processMsg(self, msg.body);
          }
        });
      }
      else {
        //console.log("ws rpc client received message = " + data);
        //console.log("ws rpc client received message len = " + data.length);
        console.log("websocket_mailbox_recv_normal: length=%s, ", data.length, data.substring(0, 300));
        var msg = {};
        var result = data;
        if (result.length > 0 && result[result.length - 1] != "}") {
          console.warn("websocket_mailbox_recv_normal: string last word is not } ");
          result = result.substring(0, result.length - 1);
        }
        msg = JSON.parse(result);

        if (Array.isArray(msg.body)) {
          processMsgs(self, msg.body);
        } else {
          processMsg(self, msg.body);
        }
      }
    } catch (e) {
      console.error('ws rpc client process message with error: %j', e.stack);
      console.log(data);
    }
  });

  this.socket.on('open', function() {
    if(self.connected) {
      //ignore reconnect
      return;
    }
    // success to connect
    self.connected = true;
    if(self.bufferMsg) {
      // start flush interval
      self._interval = setInterval(function() {
        flush(self);
      }, self.interval);
    }
    self._KPinterval = setInterval(function() {
      checkKeepAlive(self);
    }, KEEP_ALIVE_INTERVAL);
    utils.invokeCallback(cb);
  });

  this.socket.on('error', function(err) {
    utils.invokeCallback(cb, err);
    self.close();
  });

  this.socket.on('close', function(code, message) {
    var reqs = self.requests, cb;
    for(var id in reqs) {
      cb = reqs[id];
      utils.invokeCallback(cb, new Error('disconnect with remote server.'));
    }
    self.emit('close', self.id);
    self.close();
  });

//  this.socket.on('ping', function (data, flags) {
//  });
  this.socket.on('pong', function (data, flags) {
    ////console.log('ws received pong: %s', data);
    self._KP_last_pong_time = Date.now();
  });

};

var checkKeepAlive = function(mailbox) {
  if(mailbox.closed) {
    return;
  }
  var now = Date.now();
  if(mailbox._KP_last_ping_time > 0){
    //当pong的时间小于ping，说明pong还没有回来
    if(mailbox._KP_last_pong_time < mailbox._KP_last_ping_time){
      //判断是否超时
      if(now - mailbox._KP_last_ping_time > KEEP_ALIVE_TIMEOUT){
        logger.error('ws rpc client checkKeepAlive error because > KEEP_ALIVE_TIMEOUT');
        mailbox.close();
        return;
      }
      else{
        //还未超时，但是pong还没有回来，需要等下次timer再检查
        return;
      }
    }
    if(mailbox._KP_last_pong_time > mailbox._KP_last_ping_time){
      //当pong的时间大于ping，就说明对端还活着，就认为正常
      mailbox.socket.ping();
      mailbox._KP_last_ping_time = Date.now();
      return;
    }
  }
  else{
    //当一个ping都未发的时候，发一个ping
    mailbox.socket.ping();
    mailbox._KP_last_ping_time = Date.now();
  }
};

/**
 * close mailbox
 */
pro.close = function() {
  if(this.closed) {
    return;
  }
  this.closed = true;
  if(this._interval) {
    clearInterval(this._interval);
    this._interval = null;
  }
  if(this._KPinterval) {
    clearInterval(this._KPinterval);
    this._KPinterval = null;
  }
  this.socket.close();
  this._KP_last_ping_time = -1;
  this._KP_last_pong_time = -1;
};

/**
 * send message to remote server
 *
 * @param msg {service:"", method:"", args:[]}
 * @param opts {} attach info to send method
 * @param cb declaration decided by remote interface
 */
pro.send = function(tracer, msg, opts, cb) {
  tracer.info('client', __filename, 'send', 'ws-mailbox try to send');
  if(!this.connected) {
    tracer.error('client', __filename, 'send', 'ws-mailbox not init');
    utils.invokeCallback(cb, tracer, new Error('not init.'));
    return;
  }

  if(this.closed) {
    tracer.error('client', __filename, 'send', 'mailbox alread closed');
    utils.invokeCallback(cb, tracer, new Error('mailbox alread closed.'));
    return;
  }

  var id = this.curId++;
  this.requests[id] = cb;
  setCbTimeout(this, id, tracer, cb);

  var pkg;
  if(tracer.isEnabled) {
    pkg = {traceId: tracer.id, seqId: tracer.seq, source: tracer.source, remote: tracer.remote, id: id, msg: msg};
  }
  else {
    pkg = {id: id, msg: msg};
  }
  if(this.bufferMsg) {
    enqueue(this, pkg);
  } else {
    doSend(this.socket, pkg);
    //this.socket.send(JSON.stringify({body: pkg}));
  }
};

var enqueue = function(mailbox, msg) {
  mailbox.queue.push(msg);
};

var flush = function(mailbox) {
  if(mailbox.closed || !mailbox.queue.length) {
    return;
  }
  doSend(mailbox.socket, mailbox.queue);
  //mailbox.socket.send(JSON.stringify({body: mailbox.queue}));
  mailbox.queue = [];
};

var doSend = function(socket, dataObj) {
  var str = JSON.stringify({body: dataObj});
  console.log("websocket_mailbox_doSend: " + str.substring(0, 300));
  console.log("websocket_mailbox_doSend:str len = " + str.length);
  if (useZipCompress && str.length > DEFAULT_ZIP_LENGTH) {//send zip binary
    process.nextTick(function () {
      zlib.gzip(str, function (err, result) {
        if (!!err) {
          console.warn('ws rpc send message error: %j', err.stack);
          socket.send(str);
          return;
        }
//        console.log("ws rpc client send message by zip compress, buffer len = " + result.length);
        socket.send(result);
      });
    });
  }
  else {//send normal text
    //console.log("ws rpc client send message, len = " + str.length);
    socket.send(str);
  }
};

var processMsgs = function(mailbox, pkgs) {
  for(var i=0, l=pkgs.length; i<l; i++) {
    processMsg(mailbox, pkgs[i]);
  }
};

var processMsg = function(mailbox, pkg) {
  clearCbTimeout(mailbox, pkg.id);
  var cb = mailbox.requests[pkg.id];
  if(!cb) {
    return;
  }
  delete mailbox.requests[pkg.id];

  var tracer = new Tracer(mailbox.opts.rpcLogger, mailbox.opts.rpcDebugLog, mailbox.opts.clientId, pkg.source, pkg.resp, pkg.traceId, pkg.seqId);
  var args = [tracer, null];

  pkg.resp.forEach(function(arg){
    args.push(arg);
  });

  cb.apply(null, args);
};

var setCbTimeout = function(mailbox, id, tracer, cb) {
  var timer = setTimeout(function() {
    logger.warn('rpc request is timeout, id: %s, host: %s, port: %s', id, mailbox.host, mailbox.port);
    clearCbTimeout(mailbox, id);
    if(!!mailbox.requests[id]) {
      delete mailbox.requests[id];
    }
    logger.error('rpc callback timeout, remote server host: %s, port: %s', mailbox.host, mailbox.port);
    utils.invokeCallback(cb, tracer, new Error('rpc callback timeout'));
  }, mailbox.timeoutValue);
  mailbox.timeout[id] = timer;
};

var clearCbTimeout = function(mailbox, id) {
  if(!mailbox.timeout[id]) {
    logger.warn('timer is not exsits, id: %s, host: %s, port: %s', id, mailbox.host, mailbox.port);
    return;
  }
  clearTimeout(mailbox.timeout[id]);
  delete mailbox.timeout[id];
};

/**
 * Factory method to create mailbox
 *
 * @param {Object} server remote server info {id:"", host:"", port:""}
 * @param {Object} opts construct parameters
 *                      opts.bufferMsg {Boolean} msg should be buffered or send immediately.
 *                      opts.interval {Boolean} msg queue flush interval if bufferMsg is true. default is 50 ms
 */
module.exports.create = function(server, opts) {
  return new MailBox(server, opts || {});
};<commit_msg>update default settings<commit_after>var EventEmitter = require('events').EventEmitter;
var util = require('util');
var utils = require('../../util/utils');
var wsClient = require('ws');
var zlib = require('zlib');
var Tracer = require('../../util/tracer');
var logger = require('pomelo-logger').getLogger('pomelo-rpc', __filename);
var DEFAULT_CALLBACK_TIMEOUT = 25 * 1000;
var DEFAULT_INTERVAL = 50;

var KEEP_ALIVE_TIMEOUT = 10 * 1000;
var KEEP_ALIVE_INTERVAL = 30 * 1000;

var DEFAULT_ZIP_LENGTH = 1024 * 4;
var DEFAULT_USE_ZIP = false;
var useZipCompress = false;

var MailBox = function(server, opts) {
  EventEmitter.call(this);
  this.id = server.id;
  this.host = server.host;
  this.port = server.port;
  this.requests = {};
  this.timeout = {};
  this.curId = 0;
  this.queue = [];
  this.bufferMsg = opts.bufferMsg;
  this.interval = opts.interval || DEFAULT_INTERVAL;
  this.timeoutValue = opts.timeout || DEFAULT_CALLBACK_TIMEOUT;
  this.connected = false;
  this.closed = false;
  this.opts = opts;
  this._KPinterval = null;
  this._KP_last_ping_time = -1;
  this._KP_last_pong_time = -1;
  DEFAULT_ZIP_LENGTH = opts.doZipLength || DEFAULT_ZIP_LENGTH;
  useZipCompress = opts.useZipCompress || DEFAULT_USE_ZIP;
};
util.inherits(MailBox, EventEmitter);

var  pro = MailBox.prototype;

pro.connect = function(tracer, cb) {
  tracer.info('client', __filename, 'connect', 'ws-mailbox try to connect');
  if(this.connected) {
    tracer.error('client', __filename, 'connect', 'mailbox has already connected');
    utils.invokeCallback(cb, new Error('mailbox has already connected.'));
    return;
  }

  this.socket = wsClient.connect('ws://' + this.host + ':' + this.port);
  //this.socket = wsClient.connect(this.host + ':' + this.port, {'force new connection': true, 'reconnect': false});

  var self = this;
  this.socket.on('message', function(data, flags) {
    try {
      if (flags.binary) {
        console.log('ws rpc received message flags.binary, len = ', data.length);
        zlib.gunzip(data, function (err, result) {
          if (!!err) {
            console.warn('ws rpc received binary message error: %j', err.stack);
            return;
          }
          //console.log("ws rpc client received message unzip len = " + result.length);
          console.log("websocket_mailbox_recv_zip: " + result.substring(0, 300));
          var msg = {};
          if (result.length > 0 && result[result.length - 1] != "}") {
            console.warn("websocket_mailbox_recv_zip: binary last word is not } ");
            result = result.substring(0, result.length - 1);
          }
          msg = JSON.parse(result);
          if (Array.isArray(msg.body)) {
            processMsgs(self, msg.body);
          } else {
            processMsg(self, msg.body);
          }
        });
      }
      else {
        //console.log("ws rpc client received message = " + data);
        //console.log("ws rpc client received message len = " + data.length);
        console.log("websocket_mailbox_recv_normal: length=%s, ", data.length, data.substring(0, 300));
        var msg = {};
        var result = data;
        if (result.length > 0 && result[result.length - 1] != "}") {
          console.warn("websocket_mailbox_recv_normal: string last word is not } ");
          result = result.substring(0, result.length - 1);
        }
        msg = JSON.parse(result);

        if (Array.isArray(msg.body)) {
          processMsgs(self, msg.body);
        } else {
          processMsg(self, msg.body);
        }
      }
    } catch (e) {
      console.error('ws rpc client process message with error: %j', e.stack);
      console.log(data);
    }
  });

  this.socket.on('open', function() {
    if(self.connected) {
      //ignore reconnect
      return;
    }
    // success to connect
    self.connected = true;
    if(self.bufferMsg) {
      // start flush interval
      self._interval = setInterval(function() {
        flush(self);
      }, self.interval);
    }
    self._KPinterval = setInterval(function() {
      checkKeepAlive(self);
    }, KEEP_ALIVE_INTERVAL);
    utils.invokeCallback(cb);
  });

  this.socket.on('error', function(err) {
    utils.invokeCallback(cb, err);
    self.close();
  });

  this.socket.on('close', function(code, message) {
    var reqs = self.requests, cb;
    for(var id in reqs) {
      cb = reqs[id];
      utils.invokeCallback(cb, new Error('disconnect with remote server.'));
    }
    self.emit('close', self.id);
    self.close();
  });

//  this.socket.on('ping', function (data, flags) {
//  });
  this.socket.on('pong', function (data, flags) {
    ////console.log('ws received pong: %s', data);
    self._KP_last_pong_time = Date.now();
  });

};

var checkKeepAlive = function(mailbox) {
  if(mailbox.closed) {
    return;
  }
  var now = Date.now();
  if(mailbox._KP_last_ping_time > 0){
    //当pong的时间小于ping，说明pong还没有回来
    if(mailbox._KP_last_pong_time < mailbox._KP_last_ping_time){
      //判断是否超时
      if(now - mailbox._KP_last_ping_time > KEEP_ALIVE_TIMEOUT){
        logger.error('ws rpc client checkKeepAlive error because > KEEP_ALIVE_TIMEOUT');
        mailbox.close();
        return;
      }
      else{
        //还未超时，但是pong还没有回来，需要等下次timer再检查
        return;
      }
    }
    if(mailbox._KP_last_pong_time > mailbox._KP_last_ping_time){
      //当pong的时间大于ping，就说明对端还活着，就认为正常
      mailbox.socket.ping();
      mailbox._KP_last_ping_time = Date.now();
      return;
    }
  }
  else{
    //当一个ping都未发的时候，发一个ping
    mailbox.socket.ping();
    mailbox._KP_last_ping_time = Date.now();
  }
};

/**
 * close mailbox
 */
pro.close = function() {
  if(this.closed) {
    return;
  }
  this.closed = true;
  if(this._interval) {
    clearInterval(this._interval);
    this._interval = null;
  }
  if(this._KPinterval) {
    clearInterval(this._KPinterval);
    this._KPinterval = null;
  }
  this.socket.close();
  this._KP_last_ping_time = -1;
  this._KP_last_pong_time = -1;
};

/**
 * send message to remote server
 *
 * @param msg {service:"", method:"", args:[]}
 * @param opts {} attach info to send method
 * @param cb declaration decided by remote interface
 */
pro.send = function(tracer, msg, opts, cb) {
  tracer.info('client', __filename, 'send', 'ws-mailbox try to send');
  if(!this.connected) {
    tracer.error('client', __filename, 'send', 'ws-mailbox not init');
    utils.invokeCallback(cb, tracer, new Error('not init.'));
    return;
  }

  if(this.closed) {
    tracer.error('client', __filename, 'send', 'mailbox alread closed');
    utils.invokeCallback(cb, tracer, new Error('mailbox alread closed.'));
    return;
  }

  var id = this.curId++;
  this.requests[id] = cb;
  setCbTimeout(this, id, tracer, cb);

  var pkg;
  if(tracer.isEnabled) {
    pkg = {traceId: tracer.id, seqId: tracer.seq, source: tracer.source, remote: tracer.remote, id: id, msg: msg};
  }
  else {
    pkg = {id: id, msg: msg};
  }
  if(this.bufferMsg) {
    enqueue(this, pkg);
  } else {
    doSend(this.socket, pkg);
    //this.socket.send(JSON.stringify({body: pkg}));
  }
};

var enqueue = function(mailbox, msg) {
  mailbox.queue.push(msg);
};

var flush = function(mailbox) {
  if(mailbox.closed || !mailbox.queue.length) {
    return;
  }
  doSend(mailbox.socket, mailbox.queue);
  //mailbox.socket.send(JSON.stringify({body: mailbox.queue}));
  mailbox.queue = [];
};

var doSend = function(socket, dataObj) {
  var str = JSON.stringify({body: dataObj});
  console.log("websocket_mailbox_doSend: " + str.substring(0, 300));
  console.log("websocket_mailbox_doSend:str len = " + str.length);
  if (useZipCompress && str.length > DEFAULT_ZIP_LENGTH) {//send zip binary
    process.nextTick(function () {
      zlib.gzip(str, function (err, result) {
        if (!!err) {
          console.warn('ws rpc send message error: %j', err.stack);
          socket.send(str);
          return;
        }
//        console.log("ws rpc client send message by zip compress, buffer len = " + result.length);
        socket.send(result);
      });
    });
  }
  else {//send normal text
    //console.log("ws rpc client send message, len = " + str.length);
    socket.send(str);
  }
};

var processMsgs = function(mailbox, pkgs) {
  for(var i=0, l=pkgs.length; i<l; i++) {
    processMsg(mailbox, pkgs[i]);
  }
};

var processMsg = function(mailbox, pkg) {
  clearCbTimeout(mailbox, pkg.id);
  var cb = mailbox.requests[pkg.id];
  if(!cb) {
    return;
  }
  delete mailbox.requests[pkg.id];

  var tracer = new Tracer(mailbox.opts.rpcLogger, mailbox.opts.rpcDebugLog, mailbox.opts.clientId, pkg.source, pkg.resp, pkg.traceId, pkg.seqId);
  var args = [tracer, null];

  pkg.resp.forEach(function(arg){
    args.push(arg);
  });

  cb.apply(null, args);
};

var setCbTimeout = function(mailbox, id, tracer, cb) {
  var timer = setTimeout(function() {
    logger.warn('rpc request is timeout, id: %s, host: %s, port: %s', id, mailbox.host, mailbox.port);
    clearCbTimeout(mailbox, id);
    if(!!mailbox.requests[id]) {
      delete mailbox.requests[id];
    }
    logger.error('rpc callback timeout, remote server host: %s, port: %s', mailbox.host, mailbox.port);
    utils.invokeCallback(cb, tracer, new Error('rpc callback timeout'));
  }, mailbox.timeoutValue);
  mailbox.timeout[id] = timer;
};

var clearCbTimeout = function(mailbox, id) {
  if(!mailbox.timeout[id]) {
    logger.warn('timer is not exsits, id: %s, host: %s, port: %s', id, mailbox.host, mailbox.port);
    return;
  }
  clearTimeout(mailbox.timeout[id]);
  delete mailbox.timeout[id];
};

/**
 * Factory method to create mailbox
 *
 * @param {Object} server remote server info {id:"", host:"", port:""}
 * @param {Object} opts construct parameters
 *                      opts.bufferMsg {Boolean} msg should be buffered or send immediately.
 *                      opts.interval {Boolean} msg queue flush interval if bufferMsg is true. default is 50 ms
 */
module.exports.create = function(server, opts) {
  return new MailBox(server, opts || {});
};<|endoftext|>


<commit_before>'use strict';

var ArticleLoader = new Class({
    feed: 12,
    page: 0,

    initialize: function (feed) {
        this.feed = feed;

        this.getArticles();
    },

    getArticles: function () {
        new Request.JSON({
            method: 'get',
            url: window.ZerobRSS.apiUri + '/v1/feeds/' + this.feed + '/articles?page=' + this.page,
            onComplete: function (response) {
                var template = Handlebars.compile($('news-card-template').get('html'));

                response.each(function (article) {
                    var a = new Element('article');

                    a.set('data-id', article.identifier);
                    a.set('data-feed-id', article.feed_id);
                    a.set('html', template(article));

                    if (article.read) {
                        a.addClass('read');
                    }

                    a.addEvent('click', (function (e) {
                        window.ZerobRSS.ArticleLoader.clickArticle(e.target.getParent('article').get('data-id'));
                    }));

                    a.inject($('content'));
                });
            }
        }).send();
    },

    clickArticle: function (id) {
        window.ZerobRSS.Keyboard.currentArticle = id;

        $$('#content > article').each(function (elem) {
            elem.removeClass('active');
        });
        $$('article[data-id=' + id + ']')[0].addClass('active');

        new Request.JSON({
            url: window.ZerobRSS.apiUri + '/v1/articles/' + id,
            data: JSON.encode({'read': true}),
            emulation: false,
            onComplete: function (response) {
                $$('article[data-id=' + id + ']')[0].addClass('read');
            }
        }).put();
    }
});
<commit_msg>Decrease unread count when reading<commit_after>'use strict';

var ArticleLoader = new Class({
    feed: 12,
    page: 0,

    initialize: function (feed) {
        this.feed = feed;

        this.getArticles();
    },

    getArticles: function () {
        new Request.JSON({
            method: 'get',
            url: window.ZerobRSS.apiUri + '/v1/feeds/' + this.feed + '/articles?page=' + this.page,
            onComplete: function (response) {
                var template = Handlebars.compile($('news-card-template').get('html'));

                response.each(function (article) {
                    var a = new Element('article');

                    a.set('data-id', article.identifier);
                    a.set('data-feed-id', article.feed_id);
                    a.set('html', template(article));

                    if (article.read) {
                        a.addClass('read');
                    }

                    a.addEvent('click', (function (e) {
                        window.ZerobRSS.ArticleLoader.clickArticle(e.target.getParent('article').get('data-id'));
                    }));

                    a.inject($('content'));
                });
            }
        }).send();
    },

    clickArticle: function (id) {
        window.ZerobRSS.Keyboard.currentArticle = id;

        $$('#content > article').each(function (elem) {
            elem.removeClass('active');
        });

        var article = $$('article[data-id=' + id + ']')[0];

        article.addClass('active');

        new Request.JSON({
            url: window.ZerobRSS.apiUri + '/v1/articles/' + id,
            data: JSON.encode({'read': true}),
            emulation: false,
            onComplete: function (response) {
                var wasRead = article.hasClass('read');

                article.addClass('read');

                if (!wasRead) {
                    var read = $$('#aside-menu > nav > a[data-feed-id=' + article.get('data-feed-id') + '] .unread')[0];

                    read.set('html', read.get('html') - 1);
                }
            }
        }).put();
    }
});
<|endoftext|>


<commit_before>/*global OJ:true*/
(function () {

    OJ.dependsOn(['OJ.metadata.Node'], function () {

        var Div = OJ.Class('Div', OJ.metadata.Node, function () {
            var div = this;

            div.nodeName = 'DIV';
            return div;
        });

        OJ.metadata.lift('Div', Div);

        OJ.elements.lift('div', function (OjNode, options) {

            var html = '
' + OJ.to.string(options.display) + '
';
            var vendorHtml = OJ.to.vendorDomObjFromString(html);
            var div = new OJ.metadata.Div(options.id, vendorHtml[0], vendorHtml);
            OjNode.addChild(div);

            Object.defineProperty(div, 'OjVal', {
                get: function () {
                    return options.value;
                },
                set: function (val) {
                    options.value = val;
                    return val;
                }
            });

            //        div.data(options);
            //        div.addClass(options.cssclass);
            //        div.css(options.styles);
            //        div.attr(options.attr);
            //        div.prop(options.prop);

            Object.defineProperty(div, 'val', {
                value: function () {
                    return div[0].getAttribute('value');
                }
            });

            return div;

        });
    });

}());<commit_msg>remove dependsOn<commit_after>/*global OJ:true*/
(function () {

//    OJ.dependsOn(['OJ.metadata.Node'], function () {

        var Div = OJ.Class('Div', OJ.metadata.Node, function () {
            var div = this;

            div.nodeName = 'DIV';
            return div;
        });

        OJ.metadata.lift('Div', Div);

        OJ.elements.lift('div', function (OjNode, options) {

            var html = '
' + OJ.to.string(options.display) + '
';
            var vendorHtml = OJ.to.vendorDomObjFromString(html);
            var div = new OJ.metadata.Div(options.id, vendorHtml[0], vendorHtml);
            OjNode.addChild(div);

            Object.defineProperty(div, 'OjVal', {
                get: function () {
                    return options.value;
                },
                set: function (val) {
                    options.value = val;
                    return val;
                }
            });

            //        div.data(options);
            //        div.addClass(options.cssclass);
            //        div.css(options.styles);
            //        div.attr(options.attr);
            //        div.prop(options.prop);

            Object.defineProperty(div, 'val', {
                value: function () {
                    return div[0].getAttribute('value');
                }
            });

            return div;

        });
//    });

}());
<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter;
var util = require('util');
var utils = require('../../util/utils');
var wsClient = require('ws');
var zlib = require('zlib');
var Tracer = require('../../util/tracer');
var DEFAULT_CALLBACK_TIMEOUT = 10 * 1000;
var DEFAULT_INTERVAL = 50;

var KEEP_ALIVE_TIMEOUT = 10 * 1000;
var KEEP_ALIVE_INTERVAL = 30 * 1000;

var DEFAULT_ZIP_LENGTH = 1024 * 10;
var useZipCompress = false;

var MailBox = function(server, opts) {
  EventEmitter.call(this);
  this.id = server.id;
  this.host = server.host;
  this.port = server.port;
  this.requests = {};
  this.timeout = {};
  this.curId = 0;
  this.queue = [];
  this.bufferMsg = opts.bufferMsg;
  this.interval = opts.interval || DEFAULT_INTERVAL;
  this.timeoutValue = opts.timeout || DEFAULT_CALLBACK_TIMEOUT;
  this.connected = false;
  this.closed = false;
  this.opts = opts;
  this._KPinterval = null;
  this._KP_last_ping_time = -1;
  this._KP_last_pong_time = -1;
  DEFAULT_ZIP_LENGTH = opts.doZipLength || DEFAULT_ZIP_LENGTH;
  useZipCompress = opts.useZipCompress || false;
};
util.inherits(MailBox, EventEmitter);

var  pro = MailBox.prototype;

pro.connect = function(tracer, cb) {
  tracer.info('client', __filename, 'connect', 'ws-mailbox try to connect');
  if(this.connected) {
    tracer.error('client', __filename, 'connect', 'mailbox has already connected');
    utils.invokeCallback(cb, new Error('mailbox has already connected.'));
    return;
  }

  this.socket = wsClient.connect('ws://' + this.host + ':' + this.port);
  //this.socket = wsClient.connect(this.host + ':' + this.port, {'force new connection': true, 'reconnect': false});

  var self = this;
  this.socket.on('message', function(data, flags) {
    try {
      if(flags.binary){
//        //console.log('ws rpc received message flags.binary, len = ', data.length);
        zlib.gunzip(data, function (err, result) {
          if(!!err){
            console.warn('ws rpc received binary message error: %j', err.stack);
            return;
          }
          //console.log("ws rpc client received message unzip len = " + result.length);
          //console.log("ws rpc client received message unzip = " + result);
          var msg = result;

          msg = JSON.parse(msg);

          if(msg.body instanceof Array) {
            processMsgs(self, msg.body);
          } else {
            processMsg(self, msg.body);
          }
        });
      }
      else{
        //console.log("ws rpc client received message = " + data);
        var msg = data;

        msg = JSON.parse(msg);

        if(msg.body instanceof Array) {
          processMsgs(self, msg.body);
        } else {
          processMsg(self, msg.body);
        }
      }
    } catch(e) {
      console.error('ws rpc client process message with error: %j', e.stack);
    }
  });

  this.socket.on('open', function() {
    if(self.connected) {
      //ignore reconnect
      return;
    }
    // success to connect
    self.connected = true;
    if(self.bufferMsg) {
      // start flush interval
      self._interval = setInterval(function() {
        flush(self);
      }, self.interval);
    }
    self._KPinterval = setInterval(function() {
      checkKeepAlive(self);
    }, KEEP_ALIVE_INTERVAL);
    utils.invokeCallback(cb);
  });

  this.socket.on('error', function(err) {
    utils.invokeCallback(cb, err);
    self.close();
  });

  this.socket.on('close', function(code, message) {
    var reqs = self.requests, cb;
    for(var id in reqs) {
      cb = reqs[id];
      utils.invokeCallback(cb, new Error('disconnect with remote server.'));
    }
    self.emit('close', self.id);
    self.close();
  });

//  this.socket.on('ping', function (data, flags) {
//  });
  this.socket.on('pong', function (data, flags) {
    ////console.log('ws received pong: %s', data);
    self._KP_last_pong_time = Date.now();
  });

};

var checkKeepAlive = function(mailbox) {
  if(mailbox.closed) {
    return;
  }
  var now = Date.now();
  if(mailbox._KP_last_ping_time > 0){
    //pongʱСping˵pongûл
    if(mailbox._KP_last_pong_time < mailbox._KP_last_ping_time){
      //жǷʱ
      if(now - mailbox._KP_last_ping_time > KEEP_ALIVE_TIMEOUT){
        console.error('ws rpc client checkKeepAlive error because > KEEP_ALIVE_TIMEOUT');
        mailbox.close();
        return;
      }
      else{
        //δʱpongûлҪ´timerټ
        return;
      }
    }
    if(mailbox._KP_last_pong_time > mailbox._KP_last_ping_time){
      //pongʱping˵Զ˻ţΪ
      mailbox.socket.ping();
      mailbox._KP_last_ping_time = Date.now();
      return;
    }
  }
  else{
    //һpingδʱ򣬷һping
    mailbox.socket.ping();
    mailbox._KP_last_ping_time = Date.now();
  }
};

/**
 * close mailbox
 */
pro.close = function() {
  if(this.closed) {
    return;
  }
  this.closed = true;
  if(this._interval) {
    clearInterval(this._interval);
    this._interval = null;
  }
  if(this._KPinterval) {
    clearInterval(this._KPinterval);
    this._KPinterval = null;
  }
  this.socket.close();
  this._KP_last_ping_time = -1;
  this._KP_last_pong_time = -1;
};

/**
 * send message to remote server
 *
 * @param msg {service:"", method:"", args:[]}
 * @param opts {} attach info to send method
 * @param cb declaration decided by remote interface
 */
pro.send = function(tracer, msg, opts, cb) {
  tracer.info('client', __filename, 'send', 'ws-mailbox try to send');
  if(!this.connected) {
    tracer.error('client', __filename, 'send', 'ws-mailbox not init');
    utils.invokeCallback(cb, tracer, new Error('not init.'));
    return;
  }

  if(this.closed) {
    tracer.error('client', __filename, 'send', 'mailbox alread closed');
    utils.invokeCallback(cb, tracer, new Error('mailbox alread closed.'));
    return;
  }

  var id = this.curId++;
  this.requests[id] = cb;
  setCbTimeout(this, id);

  var pkg;
  if(tracer.isEnabled) {
    pkg = {traceId: tracer.id, seqId: tracer.seq, source: tracer.source, remote: tracer.remote, id: id, msg: msg};
  }
  else {
    pkg = {id: id, msg: msg};
  }
  if(this.bufferMsg) {
    enqueue(this, pkg);
  } else {
    doSend(this.socket, pkg);
    //this.socket.send(JSON.stringify({body: pkg}));
  }
};

var enqueue = function(mailbox, msg) {
  mailbox.queue.push(msg);
};

var flush = function(mailbox) {
  if(mailbox.closed || !mailbox.queue.length) {
    return;
  }
  doSend(mailbox.socket, mailbox.queue);
  //mailbox.socket.send(JSON.stringify({body: mailbox.queue}));
  mailbox.queue = [];
};

var doSend = function(socket, dataObj) {
  var str = JSON.stringify({body: dataObj});
  //console.log("ws rpc client send str = " + str);
  //console.log("ws rpc client send str len = " + str.length);
  if(useZipCompress && str.length > DEFAULT_ZIP_LENGTH){//send zip binary
    process.nextTick(function(){
      zlib.gzip(str, function (err, result) {
        if(!!err){
          console.warn('ws rpc send message error: %j', err.stack);
          socket.send(str);
          return;
        }
//        console.log("ws rpc client send message by zip compress, buffer len = " + result.length);
        socket.send(result);
      });
    });
  }
  else{//send normal text
    //console.log("ws rpc client send message, len = " + str.length);
    socket.send(str);
  }
};

var processMsgs = function(mailbox, pkgs) {
  for(var i=0, l=pkgs.length; i<l; i++) {
    processMsg(mailbox, pkgs[i]);
  }
};

var processMsg = function(mailbox, pkg) {
  clearCbTimeout(mailbox, pkg.id);
  var cb = mailbox.requests[pkg.id];
  if(!cb) {
    return;
  }
  delete mailbox.requests[pkg.id];

  var tracer = new Tracer(mailbox.opts.rpcLogger, mailbox.opts.rpcDebugLog, mailbox.opts.clientId, pkg.source, pkg.resp, pkg.traceId, pkg.seqId);
  var args = [tracer, null];

  pkg.resp.forEach(function(arg){
    args.push(arg);
  });

  cb.apply(null, args);
};

var setCbTimeout = function(mailbox, id) {
  var timer = setTimeout(function() {
    clearCbTimeout(mailbox, id);
    if(!!mailbox.requests[id]) {
      delete mailbox.requests[id];
    }
  }, mailbox.timeoutValue);
  mailbox.timeout[id] = timer;
};

var clearCbTimeout = function(mailbox, id) {
  if(!mailbox.timeout[id]) {
    console.warn('timer is not exsits, id: %s', id);
    return;
  }
  clearTimeout(mailbox.timeout[id]);
  delete mailbox.timeout[id];
};

/**
 * Factory method to create mailbox
 *
 * @param {Object} server remote server info {id:"", host:"", port:""}
 * @param {Object} opts construct parameters
 *                      opts.bufferMsg {Boolean} msg should be buffered or send immediately.
 *                      opts.interval {Boolean} msg queue flush interval if bufferMsg is true. default is 50 ms
 */
module.exports.create = function(server, opts) {
  return new MailBox(server, opts || {});
};<commit_msg>rpc request timeout 15s<commit_after>var EventEmitter = require('events').EventEmitter;
var util = require('util');
var utils = require('../../util/utils');
var wsClient = require('ws');
var zlib = require('zlib');
var Tracer = require('../../util/tracer');
var logger = require('pomelo-logger').getLogger('pomelo-rpc', __filename);
var DEFAULT_CALLBACK_TIMEOUT = 15 * 1000;
var DEFAULT_INTERVAL = 50;

var KEEP_ALIVE_TIMEOUT = 10 * 1000;
var KEEP_ALIVE_INTERVAL = 30 * 1000;

var DEFAULT_ZIP_LENGTH = 1024 * 10;
var useZipCompress = false;

var MailBox = function(server, opts) {
  EventEmitter.call(this);
  this.id = server.id;
  this.host = server.host;
  this.port = server.port;
  this.requests = {};
  this.timeout = {};
  this.curId = 0;
  this.queue = [];
  this.bufferMsg = opts.bufferMsg;
  this.interval = opts.interval || DEFAULT_INTERVAL;
  this.timeoutValue = opts.timeout || DEFAULT_CALLBACK_TIMEOUT;
  this.connected = false;
  this.closed = false;
  this.opts = opts;
  this._KPinterval = null;
  this._KP_last_ping_time = -1;
  this._KP_last_pong_time = -1;
  DEFAULT_ZIP_LENGTH = opts.doZipLength || DEFAULT_ZIP_LENGTH;
  useZipCompress = opts.useZipCompress || false;
};
util.inherits(MailBox, EventEmitter);

var  pro = MailBox.prototype;

pro.connect = function(tracer, cb) {
  tracer.info('client', __filename, 'connect', 'ws-mailbox try to connect');
  if(this.connected) {
    tracer.error('client', __filename, 'connect', 'mailbox has already connected');
    utils.invokeCallback(cb, new Error('mailbox has already connected.'));
    return;
  }

  this.socket = wsClient.connect('ws://' + this.host + ':' + this.port);
  //this.socket = wsClient.connect(this.host + ':' + this.port, {'force new connection': true, 'reconnect': false});

  var self = this;
  this.socket.on('message', function(data, flags) {
    try {
      if(flags.binary){
//        //console.log('ws rpc received message flags.binary, len = ', data.length);
        zlib.gunzip(data, function (err, result) {
          if(!!err){
            logger.warn('ws rpc received binary message error: %j', err.stack);
            return;
          }
          //console.log("ws rpc client received message unzip len = " + result.length);
          //console.log("ws rpc client received message unzip = " + result);
          var msg = result;

          msg = JSON.parse(msg);

          if(msg.body instanceof Array) {
            processMsgs(self, msg.body);
          } else {
            processMsg(self, msg.body);
          }
        });
      }
      else{
        //console.log("ws rpc client received message = " + data);
        var msg = data;

        msg = JSON.parse(msg);

        if(msg.body instanceof Array) {
          processMsgs(self, msg.body);
        } else {
          processMsg(self, msg.body);
        }
      }
    } catch(e) {
      logger.error('ws rpc client process message with error: %j', e.stack);
    }
  });

  this.socket.on('open', function() {
    if(self.connected) {
      //ignore reconnect
      return;
    }
    // success to connect
    self.connected = true;
    if(self.bufferMsg) {
      // start flush interval
      self._interval = setInterval(function() {
        flush(self);
      }, self.interval);
    }
    self._KPinterval = setInterval(function() {
      checkKeepAlive(self);
    }, KEEP_ALIVE_INTERVAL);
    utils.invokeCallback(cb);
  });

  this.socket.on('error', function(err) {
    utils.invokeCallback(cb, err);
    self.close();
  });

  this.socket.on('close', function(code, message) {
    var reqs = self.requests, cb;
    for(var id in reqs) {
      cb = reqs[id];
      utils.invokeCallback(cb, new Error('disconnect with remote server.'));
    }
    self.emit('close', self.id);
    self.close();
  });

//  this.socket.on('ping', function (data, flags) {
//  });
  this.socket.on('pong', function (data, flags) {
    ////console.log('ws received pong: %s', data);
    self._KP_last_pong_time = Date.now();
  });

};

var checkKeepAlive = function(mailbox) {
  if(mailbox.closed) {
    return;
  }
  var now = Date.now();
  if(mailbox._KP_last_ping_time > 0){
    //当pong的时间小于ping，说明pong还没有回来
    if(mailbox._KP_last_pong_time < mailbox._KP_last_ping_time){
      //判断是否超时
      if(now - mailbox._KP_last_ping_time > KEEP_ALIVE_TIMEOUT){
        logger.error('ws rpc client checkKeepAlive error because > KEEP_ALIVE_TIMEOUT');
        mailbox.close();
        return;
      }
      else{
        //还未超时，但是pong还没有回来，需要等下次timer再检查
        return;
      }
    }
    if(mailbox._KP_last_pong_time > mailbox._KP_last_ping_time){
      //当pong的时间大于ping，就说明对端还活着，就认为正常
      mailbox.socket.ping();
      mailbox._KP_last_ping_time = Date.now();
      return;
    }
  }
  else{
    //当一个ping都未发的时候，发一个ping
    mailbox.socket.ping();
    mailbox._KP_last_ping_time = Date.now();
  }
};

/**
 * close mailbox
 */
pro.close = function() {
  if(this.closed) {
    return;
  }
  this.closed = true;
  if(this._interval) {
    clearInterval(this._interval);
    this._interval = null;
  }
  if(this._KPinterval) {
    clearInterval(this._KPinterval);
    this._KPinterval = null;
  }
  this.socket.close();
  this._KP_last_ping_time = -1;
  this._KP_last_pong_time = -1;
};

/**
 * send message to remote server
 *
 * @param msg {service:"", method:"", args:[]}
 * @param opts {} attach info to send method
 * @param cb declaration decided by remote interface
 */
pro.send = function(tracer, msg, opts, cb) {
  tracer.info('client', __filename, 'send', 'ws-mailbox try to send');
  if(!this.connected) {
    tracer.error('client', __filename, 'send', 'ws-mailbox not init');
    utils.invokeCallback(cb, tracer, new Error('not init.'));
    return;
  }

  if(this.closed) {
    tracer.error('client', __filename, 'send', 'mailbox alread closed');
    utils.invokeCallback(cb, tracer, new Error('mailbox alread closed.'));
    return;
  }

  var id = this.curId++;
  this.requests[id] = cb;
  setCbTimeout(this, id, tracer, cb);

  var pkg;
  if(tracer.isEnabled) {
    pkg = {traceId: tracer.id, seqId: tracer.seq, source: tracer.source, remote: tracer.remote, id: id, msg: msg};
  }
  else {
    pkg = {id: id, msg: msg};
  }
  if(this.bufferMsg) {
    enqueue(this, pkg);
  } else {
    doSend(this.socket, pkg);
    //this.socket.send(JSON.stringify({body: pkg}));
  }
};

var enqueue = function(mailbox, msg) {
  mailbox.queue.push(msg);
};

var flush = function(mailbox) {
  if(mailbox.closed || !mailbox.queue.length) {
    return;
  }
  doSend(mailbox.socket, mailbox.queue);
  //mailbox.socket.send(JSON.stringify({body: mailbox.queue}));
  mailbox.queue = [];
};

var doSend = function(socket, dataObj) {
  var str = JSON.stringify({body: dataObj});
  //console.log("ws rpc client send str = " + str);
  //console.log("ws rpc client send str len = " + str.length);
  if(useZipCompress && str.length > DEFAULT_ZIP_LENGTH){//send zip binary
    process.nextTick(function(){
      zlib.gzip(str, function (err, result) {
        if(!!err){
          logger.warn('ws rpc send message error: %j', err.stack);
          socket.send(str);
          return;
        }
//        console.log("ws rpc client send message by zip compress, buffer len = " + result.length);
        socket.send(result);
      });
    });
  }
  else{//send normal text
    //console.log("ws rpc client send message, len = " + str.length);
    socket.send(str);
  }
};

var processMsgs = function(mailbox, pkgs) {
  for(var i=0, l=pkgs.length; i<l; i++) {
    processMsg(mailbox, pkgs[i]);
  }
};

var processMsg = function(mailbox, pkg) {
  clearCbTimeout(mailbox, pkg.id);
  var cb = mailbox.requests[pkg.id];
  if(!cb) {
    return;
  }
  delete mailbox.requests[pkg.id];

  var tracer = new Tracer(mailbox.opts.rpcLogger, mailbox.opts.rpcDebugLog, mailbox.opts.clientId, pkg.source, pkg.resp, pkg.traceId, pkg.seqId);
  var args = [tracer, null];

  pkg.resp.forEach(function(arg){
    args.push(arg);
  });

  cb.apply(null, args);
};

var setCbTimeout = function(mailbox, id, tracer, cb) {
  var timer = setTimeout(function() {
    logger.warn('rpc request is timeout, id: %s, host: %s, port: %s', id, mailbox.host, mailbox.port);
    clearCbTimeout(mailbox, id);
    if(!!mailbox.requests[id]) {
      delete mailbox.requests[id];
    }
    logger.error('rpc callback timeout, remote server host: %s, port: %s', mailbox.host, mailbox.port);
    utils.invokeCallback(cb, tracer, new Error('rpc callback timeout'));
  }, mailbox.timeoutValue);
  mailbox.timeout[id] = timer;
};

var clearCbTimeout = function(mailbox, id) {
  if(!mailbox.timeout[id]) {
    logger.warn('timer is not exsits, id: %s, host: %s, port: %s', id, mailbox.host, mailbox.port);
    return;
  }
  clearTimeout(mailbox.timeout[id]);
  delete mailbox.timeout[id];
};

/**
 * Factory method to create mailbox
 *
 * @param {Object} server remote server info {id:"", host:"", port:""}
 * @param {Object} opts construct parameters
 *                      opts.bufferMsg {Boolean} msg should be buffered or send immediately.
 *                      opts.interval {Boolean} msg queue flush interval if bufferMsg is true. default is 50 ms
 */
module.exports.create = function(server, opts) {
  return new MailBox(server, opts || {});
};<|endoftext|>