<commit_before>var prototype = Object.create(HTMLElement.prototype, {
  editable: {
    get: function(){
      return this.getAttribute('editable') == 'true';
    }
  },

  config: {
    get: function(){
      return this.readAttributeAsJson('data-config');
    }
  },

  src: {
    get: function(){
      return this.getAttribute('src') || 'about:blank';
    }
  },

  // TODO
  // pass this from the player
  componentName: {
    get: function(){
      return this.getAttribute('component-name');
    }
  }
});

prototype.readAttributeAsJson = function(name) {
  if(!this.hasAttribute(name)) { return {}; }
  return JSON.parse(this.getAttribute(name));
};

prototype.createdCallback = function() {
  var componentName = this.componentName || 'vs-texthd';
  if(componentName !== 'vs-texthd') {
    console.error('Component Launcher is not ready for anything rather than vs-texthd');
    return;
  }
  this.childComponent = document.createElement(componentName);
  this.appendChild(this.childComponent);
};

prototype.attachedCallback = function(){
  // import versal.html which has definitions for custom elements
  var link = document.createElement('link');
  link.rel = 'import';
  link.href = this.src;
  document.head.appendChild(link);

  this.initObserver();

  this.initChild();

  // necessary to remove spinner
  this.fireCustomEvent('rendered');
};

prototype.childAtSameStateAsLauncher = function(){
  return this.childComponent.getAttribute('data-config') === this.getAttribute('data-config');
};

prototype.initObserver = function(){
  var sendAttributesToPlayer = function(mutation){
    if(mutation.type === 'attributes' && mutation.attributeName === 'data-config') {
      var config = mutation.target.getAttribute('data-config');

      // return if child component is already at the correct state
      if(this.childAtSameStateAsLauncher()) {
        return;
      }

      this.fireCustomEvent('setAttributes', JSON.parse(config));
    }
  }.bind(this);

  // create an observer instance
  this.observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      sendAttributesToPlayer(mutation);
    });
  });

  // pass in the target node, as well as the observer options
  this.observer.observe(this.childComponent, { attributes: true });
};

prototype.initChild = function(){
  this.setChildEditable();
  this.setChildConfig();
};

prototype.detachedCallback = function(){
  this.observer.disconnect();
  this.removeChild(this.childComponent);
};

prototype.setChildEditable = function(){
  if(this.editable) {
    this.childComponent.setAttribute('editable', 'true');
  } else {
    this.childComponent.removeAttribute('editable');
  }
};

prototype.setChildConfig = function(){
  // return if child component is already at the correct state
  if(this.childAtSameStateAsLauncher()) {
    return;
  }

  this.childComponent.setAttribute('data-config', this.getAttribute('data-config'));
};

prototype.attributeChangedCallback = function(name){
  switch(name) {
    case 'editable':
      this.setChildEditable();
      break;
    case 'data-config':
      this.setChildConfig();
      break;
    default:
      break;
  }
};

prototype.fireCustomEvent = function(eventName, data, options) {
  options = options || {};
  var evt = new CustomEvent(eventName, { detail: data, bubbles: options.bubbles || false });
  this.dispatchEvent(evt);
};

document.registerElement('versal-component-launcher', { prototype: prototype });
<commit_msg>Remove default<commit_after>var prototype = Object.create(HTMLElement.prototype, {
  editable: {
    get: function(){
      return this.getAttribute('editable') == 'true';
    }
  },

  config: {
    get: function(){
      return this.readAttributeAsJson('data-config');
    }
  },

  src: {
    get: function(){
      return this.getAttribute('src') || 'about:blank';
    }
  },

  // TODO
  // pass this from the player
  componentName: {
    get: function(){
      return this.getAttribute('component-name');
    }
  }
});

prototype.readAttributeAsJson = function(name) {
  if(!this.hasAttribute(name)) { return {}; }
  return JSON.parse(this.getAttribute(name));
};

prototype.createdCallback = function() {
  var componentName = this.componentName || 'vs-texthd';
  if(componentName !== 'vs-texthd') {
    console.error('Component Launcher is not ready for anything rather than vs-texthd');
    return;
  }
  this.childComponent = document.createElement(componentName);
  this.appendChild(this.childComponent);
};

prototype.attachedCallback = function(){
  // import versal.html which has definitions for custom elements
  var link = document.createElement('link');
  link.rel = 'import';
  link.href = this.src;
  document.head.appendChild(link);

  this.initObserver();

  this.initChild();

  // necessary to remove spinner
  this.fireCustomEvent('rendered');
};

prototype.childAtSameStateAsLauncher = function(){
  return this.childComponent.getAttribute('data-config') === this.getAttribute('data-config');
};

prototype.initObserver = function(){
  var sendAttributesToPlayer = function(mutation){
    if(mutation.type === 'attributes' && mutation.attributeName === 'data-config') {
      var config = mutation.target.getAttribute('data-config');

      // return if child component is already at the correct state
      if(this.childAtSameStateAsLauncher()) {
        return;
      }

      this.fireCustomEvent('setAttributes', JSON.parse(config));
    }
  }.bind(this);

  // create an observer instance
  this.observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      sendAttributesToPlayer(mutation);
    });
  });

  // pass in the target node, as well as the observer options
  this.observer.observe(this.childComponent, { attributes: true });
};

prototype.initChild = function(){
  this.setChildEditable();
  this.setChildConfig();
};

prototype.detachedCallback = function(){
  this.observer.disconnect();
  this.removeChild(this.childComponent);
};

prototype.setChildEditable = function(){
  if(this.editable) {
    this.childComponent.setAttribute('editable', 'true');
  } else {
    this.childComponent.removeAttribute('editable');
  }
};

prototype.setChildConfig = function(){
  // return if child component is already at the correct state
  if(this.childAtSameStateAsLauncher()) {
    return;
  }

  this.childComponent.setAttribute('data-config', this.getAttribute('data-config'));
};

prototype.attributeChangedCallback = function(name){
  switch(name) {
    case 'editable':
      this.setChildEditable();
      break;
    case 'data-config':
      this.setChildConfig();
      break;
  }
};

prototype.fireCustomEvent = function(eventName, data, options) {
  options = options || {};
  var evt = new CustomEvent(eventName, { detail: data, bubbles: options.bubbles || false });
  this.dispatchEvent(evt);
};

document.registerElement('versal-component-launcher', { prototype: prototype });
<|endoftext|>


<commit_before>'use strict';
var assert = require('assert');
var connect = require('./rest_template').connect;

var utils = require('./suites/utils');
var getParameters = utils.getParameters;
var patchParameters = utils.patchParameters;
var generateDependencies = utils.generateDependencies;

var createClientAndUser = require('./test_invoice_db').createClientAndUser;

describe('Invoice REST API', function() {
  var urlRoot = 'http://localhost';
  var port = 3456;
  var resourceName = 'invoice';

  connect(urlRoot, port, resourceName);

  it('should fail with an empty invoice POST', function(done) {
    this.resource.post().then(function() {
      assert(false, 'Posted ' + resourceName + ' even though shouldn\'t');
    }).catch(function(res) {
      var data = res.data;

      assert(true, 'Failed to post ' + resourceName + ' as expected');

      assert.equal(res.status, 422);
      assert(data.message, 'Error message exists');
      assert(data.errors, 'Errors exist');
      assert(data.warnings, 'Warnings exist');
    }).finally(done);
  });

  it('should be able to POST an invoice', function(done) {
    createInvoice(this.client, this.schema, this.resource, null, function(err) {
      if(err) {
        return done(err);
      }

      assert(true, 'Posted ' + resourceName + ' as expected');

      done();
    });
  });

  it('should be able to GET an invoice', function(done) {
    this.resource.get().then(function(res) {
      assert(res.data.length === 0, 'Failed to get ' + resourceName + 's as expected');

      done();
    }).catch(done);
  });

  it('should be able to PUT an invoice', function(done) {
    this.resource.put().then(function() {
      assert(false, 'Updated ' + resourceName + ' even though shouldn\'t');
    }).catch(function() {
      assert(true, 'Failed to update ' + resourceName + ' as expected');
    }).finally(done);
  });

  it('should be able to update status', function(done) {
    // XXX: bad API design - this should be cleaned up
    createInvoice(this.client, this.schema, this.resource, {
      field: 'status',
      value: 'pending'
    }, function(err, invoice) {
      if(err) {
        return done(err);
      }

      var data = invoice.data;

      data.status = 'approved';

      this.resource.put(data).then(function(d) {
        assert(d.data === data.status, 'Wrong status');
      }).catch(function() {
        assert(false, 'Failed to update status');
      }).finally(done);
    }.bind(this));
  });

  it('should be able to approve an invoice', function(done) {
    // TODO
    done();
  });

  it('should be able to mark an invoice as paid', function(done) {
    // TODO
    done();
  });
});

function createInvoice(apiClient, schema, resource, extraField, cb) {
  var postSchema = resource.post.parameters[0].schema;

  createClientAndUser(function(client, user) {
    generateDependencies(apiClient, schema, postSchema).then(function(d) {
      d.push({
        field: 'receiver',
        value: client.id
      });
      d.push({
        field: 'sender',
        value: user.id
      });
      d.push({
        field: 'due',
        value: new Date()
      });

      if(extraField) {
        d.push(extraField);
      }

      resource.post(
        patchParameters(getParameters(postSchema), d)
      ).then(function(invoice) {
        cb(null, invoice);
      }).catch(cb);
    }).catch(cb);
  });
}
<commit_msg>Add test stubs<commit_after>'use strict';
var assert = require('assert');
var connect = require('./rest_template').connect;

var utils = require('./suites/utils');
var getParameters = utils.getParameters;
var patchParameters = utils.patchParameters;
var generateDependencies = utils.generateDependencies;

var createClientAndUser = require('./test_invoice_db').createClientAndUser;

describe('Invoice REST API', function() {
  var urlRoot = 'http://localhost';
  var port = 3456;
  var resourceName = 'invoice';

  connect(urlRoot, port, resourceName);

  it('should fail with an empty invoice POST', function(done) {
    this.resource.post().then(function() {
      assert(false, 'Posted ' + resourceName + ' even though shouldn\'t');
    }).catch(function(res) {
      var data = res.data;

      assert(true, 'Failed to post ' + resourceName + ' as expected');

      assert.equal(res.status, 422);
      assert(data.message, 'Error message exists');
      assert(data.errors, 'Errors exist');
      assert(data.warnings, 'Warnings exist');
    }).finally(done);
  });

  it('should be able to POST an invoice', function(done) {
    createInvoice(this.client, this.schema, this.resource, null, function(err) {
      if(err) {
        return done(err);
      }

      assert(true, 'Posted ' + resourceName + ' as expected');

      done();
    });
  });

  it('should be able to GET an invoice', function(done) {
    this.resource.get().then(function(res) {
      assert(res.data.length === 0, 'Failed to get ' + resourceName + 's as expected');

      done();
    }).catch(done);
  });

  it('should be able to PUT an invoice', function(done) {
    this.resource.put().then(function() {
      assert(false, 'Updated ' + resourceName + ' even though shouldn\'t');
    }).catch(function() {
      assert(true, 'Failed to update ' + resourceName + ' as expected');
    }).finally(done);
  });

  it('should be able to update status', function(done) {
    // XXX: bad API design - this should be cleaned up
    createInvoice(this.client, this.schema, this.resource, {
      field: 'status',
      value: 'pending'
    }, function(err, invoice) {
      if(err) {
        return done(err);
      }

      var data = invoice.data;

      data.status = 'approved';

      this.resource.put(data).then(function(d) {
        assert(d.data === data.status, 'Wrong status');
      }).catch(function() {
        assert(false, 'Failed to update status');
      }).finally(done);
    }.bind(this));
  });

  it('should be able to approve an invoice', function(done) {
    // TODO
    done();
  });

  it('should be able to mark an invoice as paid', function(done) {
    // TODO
    done();
  });

  it('should not be able to mark a approved invoice as pending', function(done) {
    // TODO
    done();
  });

  it('should not be able to mark a paid invoice as approved', function(done) {
    // TODO
    done();
  });

  it('should not be able to mark a paid invoice as pending', function(done) {
    // TODO
    done();
  });
});

function createInvoice(apiClient, schema, resource, extraField, cb) {
  var postSchema = resource.post.parameters[0].schema;

  createClientAndUser(function(client, user) {
    generateDependencies(apiClient, schema, postSchema).then(function(d) {
      d.push({
        field: 'receiver',
        value: client.id
      });
      d.push({
        field: 'sender',
        value: user.id
      });
      d.push({
        field: 'due',
        value: new Date()
      });

      if(extraField) {
        d.push(extraField);
      }

      resource.post(
        patchParameters(getParameters(postSchema), d)
      ).then(function(invoice) {
        cb(null, invoice);
      }).catch(cb);
    }).catch(cb);
  });
}
<|endoftext|>


<commit_before>// The Ghost Boot Sequence
// -----------------------
// - This is intentionally one big file at the moment, so that we don't have to follow boot logic all over the place
// - This file is FULL of debug statements so we can see timings for the various steps because the boot needs to be as fast as possible
// - As we manage to break the codebase down into distinct components for e.g. the frontend, their boot logic can be offloaded to them
// - app.js is separate as the first example of each component having it's own app.js file colocated with it, instead of inside of server/web

// IMPORTANT: The only global requires here should be overrides + debug so we can monitor timings with DEBUG=ghost:boot* node ghost
require('./server/overrides');
const debug = require('ghost-ignition').debug('boot');
// END OF GLOBAL REQUIRES

/**
 * Helper class to create consistent log messages
 */class BootLogger {
    constructor(logging, startTime) {
        this.logging = logging;
        this.startTime = startTime;
    }
    log(message) {
        let {logging, startTime} = this;
        logging.info(`Ghost ${message} in ${(Date.now() - startTime) / 1000}s`);
    }
}

/**
 * Helper function to handle sending server ready notifications
 */
function notifyServerReady(error) {
    const notify = require('./server/notify');

    if (error) {
        debug('Notifying server ready (error)');
        notify.notifyServerReady(error);
    } else {
        debug('Notifying server ready (success)');
        notify.notifyServerReady();
    }
}

/**
  * Get the Database into a ready state
  * - DatabaseStateManager handles doing all this for us
  * - Passing logging makes it output state messages
  */
async function initDatabase({config, logging}) {
    const DatabaseStateManager = require('./server/data/db/state-manager');
    const dbStateManager = new DatabaseStateManager({knexMigratorFilePath: config.get('paths:appRoot')});
    await dbStateManager.makeReady({logging});
}

/**
 * Core is intended to be all the bits of Ghost that are fundamental and we can't do anything without them!
 * (There's more to do to make this true)
 */
async function initCore({ghostServer}) {
    debug('Begin: initCore');
    // Models are the heart of Ghost - this is a syncronous operation
    debug('Begin: models');
    const models = require('./server/models');
    models.init();
    debug('End: models');

    // Settings are a core concept we use settings to store key-value pairs used in critical pathways as well as public data like the site title
    debug('Begin: settings');
    const settings = require('./server/services/settings');
    await settings.init();
    debug('End: settings');

    // The URLService is a core part of Ghost, which depends on models. It needs moving from the frontend to make this clear.
    debug('Begin: Url Service');
    const urlService = require('./frontend/services/url');
    // Note: there is no await here, we do not wait for the url service to finish
    // We can return, but the site will remain in (the shared, not global) maintenance mode until this finishes
    // This is managed on request: https://github.com/TryGhost/Ghost/blob/main/core/server/web/shared/middlewares/maintenance.js#L13
    urlService.init();
    debug('End: Url Service');

    // Job Service allows parts of Ghost to run in the background
    debug('Begin: Job Service');
    const jobService = require('./server/services/jobs');
    ghostServer.registerCleanupTask(async () => {
        await jobService.shutdown();
    });
    debug('End: Job Service');

    debug('End: initCore');
}

/**
 * Frontend is intended to be just Ghost's frontend
 * This is technically wrong currently because the theme & frontend settings services contain code used by the API to upload themes & settings
 */
async function initFrontend() {
    debug('Begin: initFrontend');

    debug('Begin: Frontend Settings');
    const frontendSettings = require('./frontend/services/settings');
    await frontendSettings.init();
    debug('End: Frontend Settings');

    debug('Begin: Themes');
    const themeService = require('./server/services/themes');
    await themeService.init();
    debug('End: Themes');

    debug('End: initFrontend');
}

/**
 * At the moment we load our express apps all in one go, they require themselves and are co-located
 * What we want is to be able to optionally load various components and mount them
 * So eventually this function should go away
 */
async function initExpressApps() {
    debug('Begin: initExpressApps');
    const parentApp = require('./server/web/parent/app')();
    debug('End: initExpressApps');
    return parentApp;
}

/**
 * Services are components that make up part of Ghost and need initialising on boot
 * These services should all be part of core, frontend services should be loaded with the frontend
 * We are working towards this being a service loader, with the ability to make certain services optional
 */
async function initServices({config}) {
    debug('Begin: initServices');

    const defaultApiVersion = config.get('api:versions:default');
    debug(`Default API Version: ${defaultApiVersion}`);

    debug('Begin: Dynamic Routing');
    // Dynamic routing is generated from the routes.yaml file, which is part of the settings service
    // When Ghost's DB and core are loaded, we can access this file and call routing.bootstrap.start
    // However this _must_ happen after the express Apps are loaded, hence why this is here and not in initFrontend
    // Routing is currently tightly coupled between the frontend and backend
    const routing = require('./frontend/services/routing');
    const bridge = require('./bridge');
    // We pass the frontend API version here, so that the frontend services are slightly less tightly-coupled
    routing.bootstrap.start(bridge.getFrontendApiVersion());
    const settings = require('./server/services/settings');
    const frontendSettings = require('./frontend/services/settings');
    const getRoutesHash = () => frontendSettings.getCurrentHash('routes');
    await settings.syncRoutesHash(getRoutesHash);
    debug('End: Dynamic Routing');

    debug('Begin: Services');
    const permissions = require('./server/services/permissions');
    const xmlrpc = require('./server/services/xmlrpc');
    const slack = require('./server/services/slack');
    const {mega} = require('./server/services/mega');
    const webhooks = require('./server/services/webhooks');
    const appService = require('./frontend/services/apps');
    const limits = require('./server/services/limits');
    const scheduling = require('./server/adapters/scheduling');

    const urlUtils = require('./shared/url-utils');

    await Promise.all([
        permissions.init(),
        xmlrpc.listen(),
        slack.listen(),
        mega.listen(),
        webhooks.listen(),
        appService.init(),
        limits.init(),
        scheduling.init({
            apiUrl: urlUtils.urlFor('api', {version: defaultApiVersion, versionType: 'admin'}, true)
        })
    ]);
    debug('End: Services');

    // Initialise analytics events
    if (config.get('segment:key')) {
        require('./server/analytics-events').init();
    }

    debug('End: initServices');
}

/**
 * Kick off recurring jobs and background services
 * These are things that happen on boot, but we don't need to wait for them to finish
 * Later, this might be a service hook
 */
async function initBackgroundServices({config}) {
    debug('Begin: initBackgroundServices');

    // we don't want to kick off background services that will interfere with tests
    if (process.env.NODE_ENV.match(/^testing/)) {
        return;
    }

    // Load email analytics recurring jobs
    if (config.get('backgroundJobs:emailAnalytics')) {
        const emailAnalyticsJobs = require('./server/services/email-analytics/jobs');
        await emailAnalyticsJobs.scheduleRecurringJobs();
    }

    // Load all inactive themes
    const themeService = require('./server/services/themes');
    themeService.loadInactiveThemes();

    debug('End: initBackgroundServices');
}

/**
 * ----------------------------------
 * Boot Ghost - The magic starts here
 * ----------------------------------
 *
 * - This function is written with async/await so you can read, line by line, what happens on boot
 * - All the functions above handle init/boot logic for a single component
 */
async function bootGhost() {
    // Metrics
    const startTime = Date.now();
    debug('Begin Boot');

    // We need access to these variables in both the try and catch block
    let bootLogger;
    let config;
    let ghostServer;
    let logging;

    // These require their own try-catch block and error format, because we can't log an error if logging isn't working
    try {
        // Step 0 - Load config and logging - fundamental required components
        // Config must be the first thing we do, because it is required for absolutely everything
        debug('Begin: Load config');
        config = require('./shared/config');
        debug('End: Load config');

        // Logging is used absolutely everywhere
        debug('Begin: Load logging');
        logging = require('./shared/logging');
        bootLogger = new BootLogger(logging, startTime);
        debug('End: Load logging');

        // At this point logging is required, so we can handle errors better
    } catch (error) {
        console.error(error); // eslint-disable-line no-console
        process.exit(1);
    }

    try {
        // Step 1 - require more fundamental components
        // Version is required by sentry & Migration config & so is fundamental to booting
        // However, it involves reading package.json so its slow & it's here for visibility on that slowness
        debug('Begin: Load version info');
        require('./server/lib/ghost-version');
        debug('End: Load version info');

        // Sentry must be initialised early, but requires config
        debug('Begin: Load sentry');
        require('./shared/sentry');
        debug('End: Load sentry');

        // I18n is basically used to colocate all of our error message strings & required to log server start messages
        debug('Begin: i18n');
        const i18n = require('./shared/i18n');
        i18n.init();
        debug('End: i18n');

        debug('Begin: Load urlUtils');
        const urlUtils = require('./shared/url-utils');
        debug('End: Load urlUtils');

        // Step 2 - Start server with minimal app in global maintenance mode
        debug('Begin: load server + minimal app');
        const rootApp = require('./app');
        const GhostServer = require('./server/ghost-server');
        ghostServer = new GhostServer({url: urlUtils.urlFor('home', true)});
        await ghostServer.start(rootApp);
        bootLogger.log('server started');
        debug('End: load server + minimal app');

        // Step 3 - Get the DB ready
        debug('Begin: Get DB ready');
        await initDatabase({config, logging});
        bootLogger.log('database ready');
        debug('End: Get DB ready');

        // Step 4 - Load Ghost with all its services
        debug('Begin: Load Ghost Services & Apps');
        await initCore({ghostServer});
        await initFrontend();
        const ghostApp = await initExpressApps({});
        await initServices({config});
        debug('End: Load Ghost Services & Apps');

        // Step 5 - Mount the full Ghost app onto the minimal root app & disable maintenance mode
        debug('Begin: mountGhost');
        rootApp.disable('maintenance');
        rootApp.use(urlUtils.getSubdir(), ghostApp);
        debug('End: mountGhost');

        // Step 6 - We are technically done here - let everyone know!
        bootLogger.log('booted');
        notifyServerReady();

        // Step 7 - Init our background services, we don't wait for this to finish
        initBackgroundServices({config});

        // We return the server purely for testing purposes
        debug('End Boot: Returning Ghost Server');
        return ghostServer;
    } catch (error) {
        const errors = require('@tryghost/errors');

        // Ensure the error we have is an ignition error
        let serverStartError = error;
        if (!errors.utils.isIgnitionError(serverStartError)) {
            serverStartError = new errors.GhostError({message: serverStartError.message, err: serverStartError});
        }

        logging.error(serverStartError);

        // If ghost was started and something else went wrong, we shut it down
        if (ghostServer) {
            notifyServerReady(serverStartError);
            ghostServer.shutdown(2);
        } else {
            // Ghost server failed to start, set a timeout to give logging a chance to flush
            setTimeout(() => {
                process.exit(2);
            }, 100);
        }
    }
}

module.exports = bootGhost;
<commit_msg>🐛 Fixed webhook initialization when over limit<commit_after>// The Ghost Boot Sequence
// -----------------------
// - This is intentionally one big file at the moment, so that we don't have to follow boot logic all over the place
// - This file is FULL of debug statements so we can see timings for the various steps because the boot needs to be as fast as possible
// - As we manage to break the codebase down into distinct components for e.g. the frontend, their boot logic can be offloaded to them
// - app.js is separate as the first example of each component having it's own app.js file colocated with it, instead of inside of server/web

// IMPORTANT: The only global requires here should be overrides + debug so we can monitor timings with DEBUG=ghost:boot* node ghost
require('./server/overrides');
const debug = require('ghost-ignition').debug('boot');
// END OF GLOBAL REQUIRES

/**
 * Helper class to create consistent log messages
 */class BootLogger {
    constructor(logging, startTime) {
        this.logging = logging;
        this.startTime = startTime;
    }
    log(message) {
        let {logging, startTime} = this;
        logging.info(`Ghost ${message} in ${(Date.now() - startTime) / 1000}s`);
    }
}

/**
 * Helper function to handle sending server ready notifications
 */
function notifyServerReady(error) {
    const notify = require('./server/notify');

    if (error) {
        debug('Notifying server ready (error)');
        notify.notifyServerReady(error);
    } else {
        debug('Notifying server ready (success)');
        notify.notifyServerReady();
    }
}

/**
  * Get the Database into a ready state
  * - DatabaseStateManager handles doing all this for us
  * - Passing logging makes it output state messages
  */
async function initDatabase({config, logging}) {
    const DatabaseStateManager = require('./server/data/db/state-manager');
    const dbStateManager = new DatabaseStateManager({knexMigratorFilePath: config.get('paths:appRoot')});
    await dbStateManager.makeReady({logging});
}

/**
 * Core is intended to be all the bits of Ghost that are fundamental and we can't do anything without them!
 * (There's more to do to make this true)
 */
async function initCore({ghostServer}) {
    debug('Begin: initCore');
    // Models are the heart of Ghost - this is a syncronous operation
    debug('Begin: models');
    const models = require('./server/models');
    models.init();
    debug('End: models');

    // Settings are a core concept we use settings to store key-value pairs used in critical pathways as well as public data like the site title
    debug('Begin: settings');
    const settings = require('./server/services/settings');
    await settings.init();
    debug('End: settings');

    // The URLService is a core part of Ghost, which depends on models. It needs moving from the frontend to make this clear.
    debug('Begin: Url Service');
    const urlService = require('./frontend/services/url');
    // Note: there is no await here, we do not wait for the url service to finish
    // We can return, but the site will remain in (the shared, not global) maintenance mode until this finishes
    // This is managed on request: https://github.com/TryGhost/Ghost/blob/main/core/server/web/shared/middlewares/maintenance.js#L13
    urlService.init();
    debug('End: Url Service');

    // Job Service allows parts of Ghost to run in the background
    debug('Begin: Job Service');
    const jobService = require('./server/services/jobs');
    ghostServer.registerCleanupTask(async () => {
        await jobService.shutdown();
    });
    debug('End: Job Service');

    debug('End: initCore');
}

/**
 * Frontend is intended to be just Ghost's frontend
 * This is technically wrong currently because the theme & frontend settings services contain code used by the API to upload themes & settings
 */
async function initFrontend() {
    debug('Begin: initFrontend');

    debug('Begin: Frontend Settings');
    const frontendSettings = require('./frontend/services/settings');
    await frontendSettings.init();
    debug('End: Frontend Settings');

    debug('Begin: Themes');
    const themeService = require('./server/services/themes');
    await themeService.init();
    debug('End: Themes');

    debug('End: initFrontend');
}

/**
 * At the moment we load our express apps all in one go, they require themselves and are co-located
 * What we want is to be able to optionally load various components and mount them
 * So eventually this function should go away
 */
async function initExpressApps() {
    debug('Begin: initExpressApps');
    const parentApp = require('./server/web/parent/app')();
    debug('End: initExpressApps');
    return parentApp;
}

/**
 * Services are components that make up part of Ghost and need initialising on boot
 * These services should all be part of core, frontend services should be loaded with the frontend
 * We are working towards this being a service loader, with the ability to make certain services optional
 */
async function initServices({config}) {
    debug('Begin: initServices');

    const defaultApiVersion = config.get('api:versions:default');
    debug(`Default API Version: ${defaultApiVersion}`);

    debug('Begin: Dynamic Routing');
    // Dynamic routing is generated from the routes.yaml file, which is part of the settings service
    // When Ghost's DB and core are loaded, we can access this file and call routing.bootstrap.start
    // However this _must_ happen after the express Apps are loaded, hence why this is here and not in initFrontend
    // Routing is currently tightly coupled between the frontend and backend
    const routing = require('./frontend/services/routing');
    const bridge = require('./bridge');
    // We pass the frontend API version here, so that the frontend services are slightly less tightly-coupled
    routing.bootstrap.start(bridge.getFrontendApiVersion());
    const settings = require('./server/services/settings');
    const frontendSettings = require('./frontend/services/settings');
    const getRoutesHash = () => frontendSettings.getCurrentHash('routes');
    await settings.syncRoutesHash(getRoutesHash);
    debug('End: Dynamic Routing');

    debug('Begin: Services');
    const permissions = require('./server/services/permissions');
    const xmlrpc = require('./server/services/xmlrpc');
    const slack = require('./server/services/slack');
    const {mega} = require('./server/services/mega');
    const webhooks = require('./server/services/webhooks');
    const appService = require('./frontend/services/apps');
    const limits = require('./server/services/limits');
    const scheduling = require('./server/adapters/scheduling');

    const urlUtils = require('./shared/url-utils');

    // NOTE: limits service has to be initialized first
    // in case it limits initialization of any other service (e.g. webhooks)
    await limits.init();

    await Promise.all([
        permissions.init(),
        xmlrpc.listen(),
        slack.listen(),
        mega.listen(),
        webhooks.listen(),
        appService.init(),
        scheduling.init({
            apiUrl: urlUtils.urlFor('api', {version: defaultApiVersion, versionType: 'admin'}, true)
        })
    ]);
    debug('End: Services');

    // Initialise analytics events
    if (config.get('segment:key')) {
        require('./server/analytics-events').init();
    }

    debug('End: initServices');
}

/**
 * Kick off recurring jobs and background services
 * These are things that happen on boot, but we don't need to wait for them to finish
 * Later, this might be a service hook
 */
async function initBackgroundServices({config}) {
    debug('Begin: initBackgroundServices');

    // we don't want to kick off background services that will interfere with tests
    if (process.env.NODE_ENV.match(/^testing/)) {
        return;
    }

    // Load email analytics recurring jobs
    if (config.get('backgroundJobs:emailAnalytics')) {
        const emailAnalyticsJobs = require('./server/services/email-analytics/jobs');
        await emailAnalyticsJobs.scheduleRecurringJobs();
    }

    // Load all inactive themes
    const themeService = require('./server/services/themes');
    themeService.loadInactiveThemes();

    debug('End: initBackgroundServices');
}

/**
 * ----------------------------------
 * Boot Ghost - The magic starts here
 * ----------------------------------
 *
 * - This function is written with async/await so you can read, line by line, what happens on boot
 * - All the functions above handle init/boot logic for a single component
 */
async function bootGhost() {
    // Metrics
    const startTime = Date.now();
    debug('Begin Boot');

    // We need access to these variables in both the try and catch block
    let bootLogger;
    let config;
    let ghostServer;
    let logging;

    // These require their own try-catch block and error format, because we can't log an error if logging isn't working
    try {
        // Step 0 - Load config and logging - fundamental required components
        // Config must be the first thing we do, because it is required for absolutely everything
        debug('Begin: Load config');
        config = require('./shared/config');
        debug('End: Load config');

        // Logging is used absolutely everywhere
        debug('Begin: Load logging');
        logging = require('./shared/logging');
        bootLogger = new BootLogger(logging, startTime);
        debug('End: Load logging');

        // At this point logging is required, so we can handle errors better
    } catch (error) {
        console.error(error); // eslint-disable-line no-console
        process.exit(1);
    }

    try {
        // Step 1 - require more fundamental components
        // Version is required by sentry & Migration config & so is fundamental to booting
        // However, it involves reading package.json so its slow & it's here for visibility on that slowness
        debug('Begin: Load version info');
        require('./server/lib/ghost-version');
        debug('End: Load version info');

        // Sentry must be initialised early, but requires config
        debug('Begin: Load sentry');
        require('./shared/sentry');
        debug('End: Load sentry');

        // I18n is basically used to colocate all of our error message strings & required to log server start messages
        debug('Begin: i18n');
        const i18n = require('./shared/i18n');
        i18n.init();
        debug('End: i18n');

        debug('Begin: Load urlUtils');
        const urlUtils = require('./shared/url-utils');
        debug('End: Load urlUtils');

        // Step 2 - Start server with minimal app in global maintenance mode
        debug('Begin: load server + minimal app');
        const rootApp = require('./app');
        const GhostServer = require('./server/ghost-server');
        ghostServer = new GhostServer({url: urlUtils.urlFor('home', true)});
        await ghostServer.start(rootApp);
        bootLogger.log('server started');
        debug('End: load server + minimal app');

        // Step 3 - Get the DB ready
        debug('Begin: Get DB ready');
        await initDatabase({config, logging});
        bootLogger.log('database ready');
        debug('End: Get DB ready');

        // Step 4 - Load Ghost with all its services
        debug('Begin: Load Ghost Services & Apps');
        await initCore({ghostServer});
        await initFrontend();
        const ghostApp = await initExpressApps({});
        await initServices({config});
        debug('End: Load Ghost Services & Apps');

        // Step 5 - Mount the full Ghost app onto the minimal root app & disable maintenance mode
        debug('Begin: mountGhost');
        rootApp.disable('maintenance');
        rootApp.use(urlUtils.getSubdir(), ghostApp);
        debug('End: mountGhost');

        // Step 6 - We are technically done here - let everyone know!
        bootLogger.log('booted');
        notifyServerReady();

        // Step 7 - Init our background services, we don't wait for this to finish
        initBackgroundServices({config});

        // We return the server purely for testing purposes
        debug('End Boot: Returning Ghost Server');
        return ghostServer;
    } catch (error) {
        const errors = require('@tryghost/errors');

        // Ensure the error we have is an ignition error
        let serverStartError = error;
        if (!errors.utils.isIgnitionError(serverStartError)) {
            serverStartError = new errors.GhostError({message: serverStartError.message, err: serverStartError});
        }

        logging.error(serverStartError);

        // If ghost was started and something else went wrong, we shut it down
        if (ghostServer) {
            notifyServerReady(serverStartError);
            ghostServer.shutdown(2);
        } else {
            // Ghost server failed to start, set a timeout to give logging a chance to flush
            setTimeout(() => {
                process.exit(2);
            }, 100);
        }
    }
}

module.exports = bootGhost;
<|endoftext|>


<commit_before>/* qml.core javascript code */

if (!_globals.core)
	_globals.core = {}

_globals.core.Object = function(parent) {
	this.parent = parent;
	this._local = {}
	this._changedHandlers = {}
	this._eventHandlers = {}
	this._animations = {}
}

_globals.core.Object.prototype._setId = function (name) {
	var p = this;
	while(p) {
		p._local[name] = this;
		p = p.parent;
	}
}

_globals.core.Object.prototype.onChanged = function (name, callback) {
	if (name in this._changedHandlers)
		this._changedHandlers[name].push(callback);
	else
		this._changedHandlers[name] = [callback];
}

_globals.core.Object.prototype._update = function(name, value) {
	if (name in this._changedHandlers) {
		var handlers = this._changedHandlers[name];
		handlers.forEach(function(callback) { callback(value); });
	}
}

_globals.core.Object.prototype.on = function (name, callback) {
	if (name in this._eventHandlers)
		this._eventHandlers[name].push(callback);
	else
		this._eventHandlers[name] = [callback];
}

_globals.core.Object.prototype._emitEvent = function(name) {
	var args = Array.prototype.slice.call(arguments);
	args.shift();
	if (name in this._eventHandlers) {
		var handlers = this._eventHandlers[name];
		handlers.forEach(function(callback) { callback.apply(this, args); });
	}
}

_globals.core.Object.prototype.get = function (name) {
	if (this.hasOwnProperty(name))
		return this[name];
	var object = this;
	while(object) {
		if (name in object._local)
			return object._local[name];
		object = object.parent;
	}
	console.log(name, this);
	throw ("invalid property requested: '" + name + "' in context of " + this);
}

_globals.core.Object.prototype.setAnimation = function (name, animation) {
	this._animations[name] = animation;
}

_globals.core.Object.prototype.getAnimation = function (name, animation) {
	return this._animations[name] || null;
}

function setup(context) {
	_globals.core.Timer.prototype._restart = function() {
		if (this._timeout) {
			clearTimeout(this._timeout);
			this._timeout = undefined;
		}
		if (this._interval) {
			clearTimeout(this._interval);
			this._interval = undefined;
		}

		if (!this.running)
			return;

		//console.log("starting timer", this.interval, this.repeat);
		var self = this;
		if (this.repeat)
			this._interval = setInterval(function() { self._emitEvent('triggered'); }, this.interval);
		else
			this._timeout = setTimeout(function() { self._emitEvent('triggered'); }, this.interval);
	}

	_globals.core.Timer.prototype._update = function(name, value) {
		switch(name) {
			case 'running': this._restart(); break;
			case 'interval': this._restart(); break;
			case 'repeat': this._restart(); break;
		}
		_globals.core.Object.prototype._update.apply(this, arguments);
	}

	_globals.core.Item.prototype.children = []

	_globals.core.Item.prototype.toScreen = function() {
		var item = this;
		var x = 0, y = 0;
		while(item) {
			x += item.x;
			y += item.y;
			item = item.parent;
		}
		return [x, y, x + this.width, y + this.height];
	}

	_globals.core.Border.prototype._update = function(name, value) {
		switch(name) {
			case 'width': this.parent.element.css({'border-width': value, 'margin-left': -value, 'margin-top': -value}); break;
			case 'color': this.parent.element.css('border-color', value); break;
		}
		_globals.core.Object.prototype._update.apply(this, arguments);
	}

	_globals.core.Item.prototype._update = function(name, value) {
		switch(name) {
			case 'width': 	this.element.css('width', value); this.right.value = this.x + value; break;
			case 'height':	this.element.css('height', value); this.bottom.value = this.y + value; break;
			case 'x':		this.element.css('left', value); this.left.value = value; this.right.value = value + this.width; break;
			case 'y':		this.element.css('top', value); this.top.value = value; this.bottom.value = value + this.height; break;
			case 'z':		this.element.css('z-index', value); break;
			case 'radius':	this.element.css('border-radius', value); break;
		}
		_globals.core.Object.prototype._update.apply(this, arguments);
	}

	_globals.core.MouseArea.prototype._onEnter = function() {
		if (!this.hoverEnabled)
			return;
		this.hovered = true;
		this._emitEvent('entered')
	}

	_globals.core.MouseArea.prototype._onExit = function() {
		if (!this.hoverEnabled)
			return;
		this.hovered = false;
		this._emitEvent('exited')
	}

	_globals.core.AnchorLine.prototype.toScreen = function() {
		return this.parent.parent.toScreen()[this.boxIndex] + this.value;
	}

	_globals.core.Anchors.prototype._update = function(name, line) {
		var self = this.parent;
		var target = line.parent;
		var anchors = this;
		var lm = anchors.leftMargin || anchors.margins;
		var rm = anchors.rightMargin || anchors.margins;
		var tm = anchors.topMargin || anchors.margins;
		var bm = anchors.bottomMargin || anchors.margins;

		var update_left = function() {
			var parent_box = self.parent.toScreen();
			//console.log(target_box, parent_box);
			self.x = line.toScreen() + lm - parent_box[0];
			if (anchors.right) {
				var right_target_box = anchors.right.parent.toScreen();
				self.width = (right_target_box[2] - parent_box[0]) - self.x - rm ;
			}
		};

		var update_right = function() {
			var parent_box = self.parent.toScreen();
			//console.log(target_box, parent_box);
			if (anchors.left) {
				var left_target_box = anchors.left.parent.toScreen();
				self.width = line.toScreen() - left_target_box[0] - lm - rm;
			}
			self.x = (line.toScreen() - parent_box[0] - self.width) + lm;
		};

		var update_top = function() {
			var parent_box = self.parent.toScreen();
			//console.log(target_box, parent_box);
			self.y = line.toScreen() + tm - parent_box[1];
			if (anchors.bottom) {
				var bottom_target_box = anchors.bottom.parent.toScreen();
				self.height = (bottom_target_box[3] - parent_box[1]) - self.y - bm;
			}
		};

		var update_bottom = function() {
			var parent_box = self.parent.toScreen();
			//console.log(target_box, parent_box);
			if (anchors.top) {
				var top_target_box = anchors.top.parent.toScreen();
				self.height = line.toScreen() - top_target_box[1] - tm - bm;
			}
			self.y = (line.toScreen() - parent_box[1] - self.height) + tm;
		};

		switch(name) {
			case 'left':
				update_left();
				target.left.onChanged('value', update_left);
				break;

			case 'right':
				update_right();
				target.right.onChanged('value', update_right);
				break;

			case 'top':
				update_top();
				target.top.onChanged('value', update_top);
				break;

			case 'bottom':
				update_bottom();
				target.bottom.onChanged('value', update_bottom);
				break;
		}
		_globals.core.Object.prototype._update.apply(this, arguments);
	}

	_globals.core.Font.prototype._update = function(name, value) {
		switch(name) {
			case 'pointSize': this.parent.element.css('font-size', value + "pt"); break;
			case 'pixelSize': this.parent.element.css('font-size', value + "px"); break;
			case 'italic': this.parent.element.css('font-style', value? 'italic': 'normal'); break;
		}
		_globals.core.Object.prototype._update.apply(this, arguments);
	}

	_globals.core.Text.prototype._update = function(name, value) {
		switch(name) {
			case 'text': this.element.text(value); break;
		}
		_globals.core.Item.prototype._update.apply(this, arguments);
	}

	_globals.core.Rectangle.prototype._update = function(name, value) {
		switch(name) {
			case 'color': this.element.css('background-color', value); break;
		}
		_globals.core.Item.prototype._update.apply(this, arguments);
	}
}

exports.Context = function() {
	_globals.core.Object.apply(this, null);

	var w = $(window).width();
	var h = $(window).height();
	console.log("window size: " + w + "x" + h);

	var body = $('body');
	var div = $("

");
	body.append(div);
	$('head').append($(""
	));

	exports.addProperty(this, 'int', 'x');
	exports.addProperty(this, 'int', 'y');
	exports.addProperty(this, 'int', 'width');
	exports.addProperty(this, 'int', 'height');

	this.element = div
	this.element.css({width: w, height: h});
	this.width = w;
	this.height = h;

	console.log("context created");
	setup(this);
}

exports.Context.prototype = Object.create(_globals.core.Object.prototype);
exports.Context.prototype.constructor = exports.Context;

exports.Context.prototype.toScreen = function() {
	return [0, 0, this.width, this.height];
}

exports.Context.prototype.start = function(name) {
	var proto;
	if (typeof name == 'string') {
		console.log('creating component...', name);
		var path = name.split('.');
		proto = _globals;
		for (var i = 0; i < path.length; ++i)
			proto = proto[path[i]]
	}
	else
		proto = name;
	var instance = Object.create(proto.prototype);
	proto.apply(instance, [this]);
	return instance;
}

exports.addProperty = function(self, type, name) {
	var value;
	var timer;
	var timeout;
	var interpolated_value;
	switch(type) {
		case 'int':			value = 0; break;
		case 'bool':		value = false; break;
		case 'real':		value = 0.0; break;
		default: if (type[0].toUpperCase() == type[0]) value = null; break;
	}
	Object.defineProperty(self, name, {
		get: function() {
			return value;
		},
		set: function(newValue) {
			var animation = self.getAnimation(name)
			if (animation && value != newValue) {
				if (timer)
					clearInterval(timer);
				if (timeout)
					clearTimeout(timeout);

				var duration = animation.duration;
				var date = new Date();
				var started = date.getTime() + date.getMilliseconds() / 1000.0;

				var src = interpolated_value !== undefined? interpolated_value: value;
				var dst = newValue;
				timer = setInterval(function() {
					var date = new Date();
					var now = date.getTime() + date.getMilliseconds() / 1000.0;
					var t = 1.0 * (now - started) / duration;
					if (t >= 1)
						t = 1;

					interpolated_value = t * (dst - src) + src;
					self._update(name, interpolated_value, src);
				});

				timeout = setTimeout(function() {
					clearInterval(timer);
					interpolated_value = undefined;
					self._update(name, dst, src);
				}, duration);
			}
			oldValue = value;
			if (oldValue != newValue) {
				value = newValue;
				if (!animation)
					self._update(name, newValue, oldValue);
			}
		},
		enumerable: true
	});
}

exports._bootstrap = function(self, name) {
	switch(name) {
		case 'core.Item':
			if (self.element)
				throw "double ctor call";
			self.element = $('
');
			self.parent.element.append(self.element);
			break;
		case 'core.MouseArea':
			self.element.hover(self._onEnter.bind(self), self._onExit.bind(self));
			break;
	}
}
<commit_msg>put 'renderer' id in root context<commit_after>/* qml.core javascript code */

if (!_globals.core)
	_globals.core = {}

_globals.core.Object = function(parent) {
	this.parent = parent;
	this._local = {}
	this._changedHandlers = {}
	this._eventHandlers = {}
	this._animations = {}
}

_globals.core.Object.prototype._setId = function (name) {
	var p = this;
	while(p) {
		p._local[name] = this;
		p = p.parent;
	}
}

_globals.core.Object.prototype.onChanged = function (name, callback) {
	if (name in this._changedHandlers)
		this._changedHandlers[name].push(callback);
	else
		this._changedHandlers[name] = [callback];
}

_globals.core.Object.prototype._update = function(name, value) {
	if (name in this._changedHandlers) {
		var handlers = this._changedHandlers[name];
		handlers.forEach(function(callback) { callback(value); });
	}
}

_globals.core.Object.prototype.on = function (name, callback) {
	if (name in this._eventHandlers)
		this._eventHandlers[name].push(callback);
	else
		this._eventHandlers[name] = [callback];
}

_globals.core.Object.prototype._emitEvent = function(name) {
	var args = Array.prototype.slice.call(arguments);
	args.shift();
	if (name in this._eventHandlers) {
		var handlers = this._eventHandlers[name];
		handlers.forEach(function(callback) { callback.apply(this, args); });
	}
}

_globals.core.Object.prototype.get = function (name) {
	if (this.hasOwnProperty(name))
		return this[name];
	var object = this;
	while(object) {
		if (name in object._local)
			return object._local[name];
		object = object.parent;
	}
	console.log(name, this);
	throw ("invalid property requested: '" + name + "' in context of " + this);
}

_globals.core.Object.prototype.setAnimation = function (name, animation) {
	this._animations[name] = animation;
}

_globals.core.Object.prototype.getAnimation = function (name, animation) {
	return this._animations[name] || null;
}

function setup(context) {
	_globals.core.Timer.prototype._restart = function() {
		if (this._timeout) {
			clearTimeout(this._timeout);
			this._timeout = undefined;
		}
		if (this._interval) {
			clearTimeout(this._interval);
			this._interval = undefined;
		}

		if (!this.running)
			return;

		//console.log("starting timer", this.interval, this.repeat);
		var self = this;
		if (this.repeat)
			this._interval = setInterval(function() { self._emitEvent('triggered'); }, this.interval);
		else
			this._timeout = setTimeout(function() { self._emitEvent('triggered'); }, this.interval);
	}

	_globals.core.Timer.prototype._update = function(name, value) {
		switch(name) {
			case 'running': this._restart(); break;
			case 'interval': this._restart(); break;
			case 'repeat': this._restart(); break;
		}
		_globals.core.Object.prototype._update.apply(this, arguments);
	}

	_globals.core.Item.prototype.children = []

	_globals.core.Item.prototype.toScreen = function() {
		var item = this;
		var x = 0, y = 0;
		while(item) {
			x += item.x;
			y += item.y;
			item = item.parent;
		}
		return [x, y, x + this.width, y + this.height];
	}

	_globals.core.Border.prototype._update = function(name, value) {
		switch(name) {
			case 'width': this.parent.element.css({'border-width': value, 'margin-left': -value, 'margin-top': -value}); break;
			case 'color': this.parent.element.css('border-color', value); break;
		}
		_globals.core.Object.prototype._update.apply(this, arguments);
	}

	_globals.core.Item.prototype._update = function(name, value) {
		switch(name) {
			case 'width': 	this.element.css('width', value); this.right.value = this.x + value; break;
			case 'height':	this.element.css('height', value); this.bottom.value = this.y + value; break;
			case 'x':		this.element.css('left', value); this.left.value = value; this.right.value = value + this.width; break;
			case 'y':		this.element.css('top', value); this.top.value = value; this.bottom.value = value + this.height; break;
			case 'z':		this.element.css('z-index', value); break;
			case 'radius':	this.element.css('border-radius', value); break;
		}
		_globals.core.Object.prototype._update.apply(this, arguments);
	}

	_globals.core.MouseArea.prototype._onEnter = function() {
		if (!this.hoverEnabled)
			return;
		this.hovered = true;
		this._emitEvent('entered')
	}

	_globals.core.MouseArea.prototype._onExit = function() {
		if (!this.hoverEnabled)
			return;
		this.hovered = false;
		this._emitEvent('exited')
	}

	_globals.core.AnchorLine.prototype.toScreen = function() {
		return this.parent.parent.toScreen()[this.boxIndex] + this.value;
	}

	_globals.core.Anchors.prototype._update = function(name, line) {
		var self = this.parent;
		var target = line.parent;
		var anchors = this;
		var lm = anchors.leftMargin || anchors.margins;
		var rm = anchors.rightMargin || anchors.margins;
		var tm = anchors.topMargin || anchors.margins;
		var bm = anchors.bottomMargin || anchors.margins;

		var update_left = function() {
			var parent_box = self.parent.toScreen();
			//console.log(target_box, parent_box);
			self.x = line.toScreen() + lm - parent_box[0];
			if (anchors.right) {
				var right_target_box = anchors.right.parent.toScreen();
				self.width = (right_target_box[2] - parent_box[0]) - self.x - rm ;
			}
		};

		var update_right = function() {
			var parent_box = self.parent.toScreen();
			//console.log(target_box, parent_box);
			if (anchors.left) {
				var left_target_box = anchors.left.parent.toScreen();
				self.width = line.toScreen() - left_target_box[0] - lm - rm;
			}
			self.x = (line.toScreen() - parent_box[0] - self.width) + lm;
		};

		var update_top = function() {
			var parent_box = self.parent.toScreen();
			//console.log(target_box, parent_box);
			self.y = line.toScreen() + tm - parent_box[1];
			if (anchors.bottom) {
				var bottom_target_box = anchors.bottom.parent.toScreen();
				self.height = (bottom_target_box[3] - parent_box[1]) - self.y - bm;
			}
		};

		var update_bottom = function() {
			var parent_box = self.parent.toScreen();
			//console.log(target_box, parent_box);
			if (anchors.top) {
				var top_target_box = anchors.top.parent.toScreen();
				self.height = line.toScreen() - top_target_box[1] - tm - bm;
			}
			self.y = (line.toScreen() - parent_box[1] - self.height) + tm;
		};

		switch(name) {
			case 'left':
				update_left();
				target.left.onChanged('value', update_left);
				break;

			case 'right':
				update_right();
				target.right.onChanged('value', update_right);
				break;

			case 'top':
				update_top();
				target.top.onChanged('value', update_top);
				break;

			case 'bottom':
				update_bottom();
				target.bottom.onChanged('value', update_bottom);
				break;
		}
		_globals.core.Object.prototype._update.apply(this, arguments);
	}

	_globals.core.Font.prototype._update = function(name, value) {
		switch(name) {
			case 'pointSize': this.parent.element.css('font-size', value + "pt"); break;
			case 'pixelSize': this.parent.element.css('font-size', value + "px"); break;
			case 'italic': this.parent.element.css('font-style', value? 'italic': 'normal'); break;
		}
		_globals.core.Object.prototype._update.apply(this, arguments);
	}

	_globals.core.Text.prototype._update = function(name, value) {
		switch(name) {
			case 'text': this.element.text(value); break;
		}
		_globals.core.Item.prototype._update.apply(this, arguments);
	}

	_globals.core.Rectangle.prototype._update = function(name, value) {
		switch(name) {
			case 'color': this.element.css('background-color', value); break;
		}
		_globals.core.Item.prototype._update.apply(this, arguments);
	}
}

exports.Context = function() {
	_globals.core.Object.apply(this, null);
	this._local['renderer'] = this;

	var w = $(window).width();
	var h = $(window).height();
	console.log("window size: " + w + "x" + h);

	var body = $('body');
	var div = $("

");
	body.append(div);
	$('head').append($(""
	));

	exports.addProperty(this, 'int', 'x');
	exports.addProperty(this, 'int', 'y');
	exports.addProperty(this, 'int', 'width');
	exports.addProperty(this, 'int', 'height');

	this.element = div
	this.element.css({width: w, height: h});
	this.width = w;
	this.height = h;

	console.log("context created");
	setup(this);
}

exports.Context.prototype = Object.create(_globals.core.Object.prototype);
exports.Context.prototype.constructor = exports.Context;

exports.Context.prototype.toScreen = function() {
	return [0, 0, this.width, this.height];
}

exports.Context.prototype.start = function(name) {
	var proto;
	if (typeof name == 'string') {
		console.log('creating component...', name);
		var path = name.split('.');
		proto = _globals;
		for (var i = 0; i < path.length; ++i)
			proto = proto[path[i]]
	}
	else
		proto = name;
	var instance = Object.create(proto.prototype);
	proto.apply(instance, [this]);
	return instance;
}

exports.addProperty = function(self, type, name) {
	var value;
	var timer;
	var timeout;
	var interpolated_value;
	switch(type) {
		case 'int':			value = 0; break;
		case 'bool':		value = false; break;
		case 'real':		value = 0.0; break;
		default: if (type[0].toUpperCase() == type[0]) value = null; break;
	}
	Object.defineProperty(self, name, {
		get: function() {
			return value;
		},
		set: function(newValue) {
			var animation = self.getAnimation(name)
			if (animation && value != newValue) {
				if (timer)
					clearInterval(timer);
				if (timeout)
					clearTimeout(timeout);

				var duration = animation.duration;
				var date = new Date();
				var started = date.getTime() + date.getMilliseconds() / 1000.0;

				var src = interpolated_value !== undefined? interpolated_value: value;
				var dst = newValue;
				timer = setInterval(function() {
					var date = new Date();
					var now = date.getTime() + date.getMilliseconds() / 1000.0;
					var t = 1.0 * (now - started) / duration;
					if (t >= 1)
						t = 1;

					interpolated_value = t * (dst - src) + src;
					self._update(name, interpolated_value, src);
				});

				timeout = setTimeout(function() {
					clearInterval(timer);
					interpolated_value = undefined;
					self._update(name, dst, src);
				}, duration);
			}
			oldValue = value;
			if (oldValue != newValue) {
				value = newValue;
				if (!animation)
					self._update(name, newValue, oldValue);
			}
		},
		enumerable: true
	});
}

exports._bootstrap = function(self, name) {
	switch(name) {
		case 'core.Item':
			if (self.element)
				throw "double ctor call";
			self.element = $('
');
			self.parent.element.append(self.element);
			break;
		case 'core.MouseArea':
			self.element.hover(self._onEnter.bind(self), self._onExit.bind(self));
			break;
	}
}
<|endoftext|>


<commit_before>define(function(require, exports, module) {
"use strict";

var util = require("./util");
var EventEmitter = require("events").EventEmitter;

/**
 * Makes sure that each messages reaches the receiver. Uses sliding windows
 * and acknowledgements to guarantee that.
 *
 * @param socket {ReconnectSocket} The transport layer for the reliable socket
 * @param options {Object} Options to specify
 *   object:
 *   seq            {Number} initial sequence number
 *   ackTimeout     {Number} time to wait in ms before auto ack received messages
 *   disconnectTimeout {Number} time to waint in away state before disconnecting
 *                           the connection
 */
var ReliableSocket = module.exports = function(socket, options) {
    EventEmitter.call(this);

    this.socket = socket;

    options = options || {};
    this.options = options;

    this.initialCongestionWindowSize = 5;
    this.congestionWindowSize = this.initialCongestionWindowSize;
    this.slowStartThreshold = options.slowStartThreshold || 300;
    this.ackTimeout = options.ackTimeout || 200;
    this.disconnectTimeout = options.disconnectTimeout || 10 * 1000;
    this.retransmissionTimeout = 3000;
    this.maxRtt = options.maxRtt || 10000;
    this.minRtt = options.minRtt || 2000;

    this.debug = options.debug || false;
    this.seq = options.seq || 1;
    this.recId = -1;
    this.connected = false;

    // messages sent while not connected
    this.buffer = [];

    // sent but not yet acknowledged messages
    this.unacked = {};

    socket.on("message", this.onMessage.bind(this));
    socket.on("away", this.onAway.bind(this));
    socket.on("back", this.onBack.bind(this));
    socket.on("error", function(e){ console.error(e.message); });

    // var that = this;
    // setInterval(function() {
    //     console.log({
    //         retransmissionTimeout: that.retransmissionTimeout,
    //         congestionWindowSize: that.congestionWindowSize,
    //         srtt: that.srtt,
    //         rttVar: that.rttVar
    //     });
    // }, 2000);
};

util.inherits(ReliableSocket, EventEmitter);

Object.defineProperty(ReliableSocket.prototype, "readyState", {
    get : function() {
        return this.socket.readyState;
    }
});

ReliableSocket.prototype.onMessage = function(msg) {
    if (typeof msg == "string") {
        try {
            msg = JSON.parse(unescape(msg));
        } catch(e) {}
    }
    this.debug && console.log("on message", msg, this.seq);

    if (!msg || !msg.ack)
        return;

    // remove acknowledged messages from our buffer
    var ack = msg.ack;
    for (var key in this.unacked) {
        var unacked = this.unacked[key];
        if (unacked.seq <= ack)
            unacked.acknowledge();
    }

    if (Object.keys(this.unacked).length < this.congestionWindowSize)
        this._flush();

    var recId = msg.seq;
    if (recId) {
        var expectedId = this.recId + 1;
        if (recId == expectedId || this.recId == -1) {
            this.recId = recId;
            this._delayedAck();
        } else if (recId < expectedId) {
            this.debug && console.log("dupe", recId, expectedId);
            // we already saw this packet. Make sure the other side knows it
            this._ack();
            return;
        } else {
            this.debug &&  console.log("miss", recId, expectedId);
            // we miss packets in between
            this._ack();
            return;
        }
    }

    if (msg.d) {
        try {
            this.emit("message", msg.d);
        } catch (e) {
            console.error(e.stack);
            // Don't let engine.io catch this, it'll consider it a parser error,
            // making us reconnect
            setTimeout(function() { throw e; });
        }
    }
};

ReliableSocket.prototype.onAway = function() {
    this.debug && console.log("away");
    this._scheduleDisconnect("client connection went away");
    this.emit("away");
};

ReliableSocket.prototype.onBack = function() {
    this.debug && console.log("back");
    this._cancelDisconnect();

    if (!this.connected) {
        this.connected = true;
        this.emit("connect");
    }
    this.emit("back");

    if (this.buffer.length)
        this._flush();
    else
        this._ack();
};

ReliableSocket.prototype._scheduleDisconnect = function(reason) {
    var that = this;
    this._cancelDisconnect();
    this.debug && console.log("schedule disconnect");
    this._disconnectTimer = setTimeout(function() {
        that.disconnect(reason);
    }, this.disconnectTimeout);
};

ReliableSocket.prototype._cancelDisconnect = function() {
    this.debug && console.log("cancel disconnect");
    clearTimeout(this._disconnectTimer);
};

ReliableSocket.prototype._delayedAck = function() {
    var that = this;
    this._cancelDelayedAck();
    this._ackTimer = setTimeout(function() {
        that._ack();
    }, this.ackTimeout);
};

ReliableSocket.prototype._cancelDelayedAck = function() {
    clearTimeout(this._ackTimer);
};

ReliableSocket.prototype._ack = function() {
    if (this.socket.readyState == "open") {
        this.debug && console.log("send ack", this.recId);
        this.socket.send(encodeURI(JSON.stringify({
            ack: this.recId
        })));
    }
};

ReliableSocket.prototype._flush = function() {
    if (this.socket.readyState == "open" && this.buffer.length) {
        var toSend = Math.min(
            this.congestionWindowSize - Object.keys(this.unacked).length,
            this.buffer.length
        );

        this.debug && console.log("flush", toSend, "messages");
        for (var i=0; i<toSend; i++) {
            var msg = this.buffer.shift();
            this._sendMessage(msg);
        }
        if (!this.buffer.length)
            this.emit("drain");
    }
};

ReliableSocket.prototype.disconnect = function(reason) {
    this.debug && console.log("disconnect");
    this.connected = false;
    this.recId = -1;

    this.buffer = [];
    for (var key in this.unacked) {
        this.unacked[key].abort();
    }
    this.unacked = {};

    this.close();
    this.emit("away");

    var err = new Error("EDISCONNECT: " + (reason || "Client disconnected?"));
    err.code = "EDISCONNECT";
    this.emit("disconnect", err);
};

ReliableSocket.prototype.close = function() {
    return this.socket.close();
};

ReliableSocket.prototype.send = function(msg) {
    this._cancelDelayedAck();
    if (this.socket.readyState == "open" && Object.keys(this.unacked).length < this.congestionWindowSize) {
        this._sendMessage(msg);
        return true;
    }
    else {
        this.debug && console.log("buffer");
        this.buffer.push(msg);
        return false;
    }
};

ReliableSocket.prototype._sendMessage = function(data) {
    var that = this;

    var msg = {
        seq: ++this.seq,
        ts: Date.now(),
        tries: 0,
        send: function() {
            msg.abort();
            that.unacked[msg.seq] = msg;
            msg.retransmissionTimer = setTimeout(function() {
                msg.tries += 1;
                that.debug && console.log("timeout", msg.seq);
                that._updateRetransmissionTimeout(0);
                that._updateCongestionWindowSize(true);

                msg.send();
            }, that.retransmissionTimeout);
            //that.debug && console.log("send", msg.serialize(), that.retransmissionTimeout);
            that.socket.send(msg.serialize());
        },
        abort: function() {
            delete that.unacked[msg.seq];
            clearTimeout(msg.retransmissionTimer);
        },
        acknowledge: function() {
            msg.abort();
            if (msg.tries > 0)
                that.debug && console.log("retry", msg.seq, that.congestionWindowSize, that.retransmissionTimeout);

            that._updateCongestionWindowSize(false);
            if (!msg.tries)
                that._updateRetransmissionTimeout(Date.now() - msg.ts);
        },
        serialize: function() {
            return escape(JSON.stringify({
                ack: that.recId,
                seq: msg.seq,
                d: data
            }));
        }
    };

    msg.send();
};

ReliableSocket.prototype._updateCongestionWindowSize = function(timeout) {
    if (timeout) {
        this.slowStartThreshold = Math.max(Math.floor(this.congestionWindowSize / 2), 50);
        this.congestionWindowSize = this.initialCongestionWindowSize;
    }
    else {
        if (this.congestionWindowSize > this.slowStartThreshold)
            this.congestionWindowSize += 1;
        else
            this.congestionWindowSize *= 2;
    }
};

// use TCP algorithm to compute the retransmission timeout
// http://de.wikipedia.org/wiki/Transmission_Control_Protocol#Retransmission_Timer
ReliableSocket.prototype._updateRetransmissionTimeout = function(rtt) {
    if (!rtt) {
        // there was a timeout
        this.retransmissionTimeout = Math.min(this.retransmissionTimeout * 2, this.maxRtt);
        this.srtt = null;
    }
    else if (!this.srtt) {
        this.srtt = rtt;
        this.rttVar = 0.5 * rtt;
        this.retransmissionTimeout = rtt * this.rttVar;
    }
    else {
        var alpha = 1/8;
        var beta = 1/4;
        this.srtt = (1-alpha) * this.srtt + alpha * rtt;
        this.rttVar = (1-beta) * this.rttVar + beta * (Math.abs(this.srtt - rtt));
        this.retransmissionTimeout = this.srtt + 4 * this.rttVar;
    }

    this.retransmissionTimeout = Math.min(Math.max(this.retransmissionTimeout, this.minRtt), this.maxRtt);
};

});<commit_msg>add stats call to kaefer to inspect the internal state<commit_after>define(function(require, exports, module) {
"use strict";

var util = require("./util");
var EventEmitter = require("events").EventEmitter;

/**
 * Makes sure that each messages reaches the receiver. Uses sliding windows
 * and acknowledgements to guarantee that.
 *
 * @param socket {ReconnectSocket} The transport layer for the reliable socket
 * @param options {Object} Options to specify
 *   object:
 *   seq            {Number} initial sequence number
 *   ackTimeout     {Number} time to wait in ms before auto ack received messages
 *   disconnectTimeout {Number} time to waint in away state before disconnecting
 *                           the connection
 */
var ReliableSocket = module.exports = function(socket, options) {
    EventEmitter.call(this);

    this.socket = socket;

    options = options || {};
    this.options = options;

    this.initialCongestionWindowSize = 5;
    this.congestionWindowSize = this.initialCongestionWindowSize;
    this.slowStartThreshold = options.slowStartThreshold || 300;
    this.ackTimeout = options.ackTimeout || 200;
    this.disconnectTimeout = options.disconnectTimeout || 10 * 1000;
    this.retransmissionTimeout = 3000;
    this.maxRtt = options.maxRtt || 10000;
    this.minRtt = options.minRtt || 2000;

    this.duplicateCount = 0;
    this.missCount = 0;
    this.connectTs = 0;

    this.debug = options.debug || false;
    this.seq = options.seq || 1;
    this.recId = -1;
    this.connected = false;

    // messages sent while not connected
    this.buffer = [];

    // sent but not yet acknowledged messages
    this.unacked = {};

    socket.on("message", this.onMessage.bind(this));
    socket.on("away", this.onAway.bind(this));
    socket.on("back", this.onBack.bind(this));
    this.on("stats", this.onStats.bind(this));
    this.on("stats_reply", this.onStatsReply.bind(this));
    socket.on("error", function(e){ console.error(e.message); });
};

util.inherits(ReliableSocket, EventEmitter);

Object.defineProperty(ReliableSocket.prototype, "readyState", {
    get : function() {
        return this.socket.readyState;
    }
});

ReliableSocket.prototype.onMessage = function(msg) {
    if (typeof msg == "string") {
        try {
            msg = JSON.parse(unescape(msg));
        } catch(e) {}
    }
    this.debug && console.log("on message", msg, this.seq);

    if (!msg || !msg.ack)
        return;

    // remove acknowledged messages from our buffer
    var ack = msg.ack;
    for (var key in this.unacked) {
        var unacked = this.unacked[key];
        if (unacked.seq <= ack)
            unacked.acknowledge();
    }

    if (Object.keys(this.unacked).length < this.congestionWindowSize)
        this._flush();

    var recId = msg.seq;
    if (recId) {
        var expectedId = this.recId + 1;
        if (recId == expectedId || this.recId == -1) {
            this.recId = recId;
            this._delayedAck();
        } else if (recId < expectedId) {
            this.debug && console.log("dupe", recId, expectedId);
            // we already saw this packet. Make sure the other side knows it
            this.duplicateCount += 1;
            this._ack();
            return;
        } else {
            this.debug &&  console.log("miss", recId, expectedId);
            // we miss packets in between
            this.missCount += 1;
            this._ack();
            return;
        }
    }

    if (msg.t) {
        this.emit(msg.t, msg);
    }
    else if (msg.d) {
        try {
            this.emit("message", msg.d);
        } catch (e) {
            console.error(e.stack);
            // Don't let engine.io catch this, it'll consider it a parser error,
            // making us reconnect
            setTimeout(function() { throw e; });
        }
    }
};

ReliableSocket.prototype.onStats = function(msg) {
    var data = msg.d || {};
    data.remote = this._getStats();
    this.send(data, "stats_reply");
};

ReliableSocket.prototype.onStatsReply = function(msg) {
    var data = msg.d || {};
    data.localEnd = this._getStats();
    var id = data.id;

    if (this.statsCallbacks[id]) {
        this.statsCallbacks[id](null, data);
        delete this.statsCallbacks[id];
    }
};

ReliableSocket.prototype.stats = function(callback) {
    if (!this.statsCallbacks) this.statsCallbacks = {};
    var id = this.seq;

    this.statsCallbacks[id] = callback;
    var data = {
        id: id,
        localStart: this._getStats()
    };
    this.send(data, "stats");
};

ReliableSocket.prototype._getStats = function() {
    var wsBuffer = -1;
    try {
        if (this.socket.socket.transport.ws)
            wsBuffer = this.socket.socket.transport.ws.bufferedAmount;
        else if (this.socket.socket.transport.socket)
            wsBuffer = this.socket.socket.transport.socket._socket.bufferSize;
    } catch(e) {}

    return {
        livetime: Date.now() - this.connectTs,
        ts: Date.now(),
        retransmissionTimeout: this.retransmissionTimeout,
        congestionWindowSize: this.congestionWindowSize,
        srtt: this.srtt,
        rttVar: this.rttVar,
        duplicateCount: this.duplicateCount,
        missCount: this.missCount,
        buffered: this.buffer.length,
        unacked: Object.keys(this.unacked).length,
        eioBuffer: this.socket.socket.writeBuffer.length,
        wsBuffer: wsBuffer
    };
};

ReliableSocket.prototype.onAway = function() {
    this.debug && console.log("away");
    this._scheduleDisconnect("client connection went away");
    this.emit("away");
};

ReliableSocket.prototype.onBack = function() {
    this.debug && console.log("back");
    this._cancelDisconnect();

    this.connectTs = Date.now();
    if (!this.connected) {
        this.connected = true;
        this.emit("connect");
    }
    this.emit("back");

    if (this.buffer.length)
        this._flush();
    else
        this._ack();
};

ReliableSocket.prototype._scheduleDisconnect = function(reason) {
    var that = this;
    this._cancelDisconnect();
    this.debug && console.log("schedule disconnect");
    this._disconnectTimer = setTimeout(function() {
        that.disconnect(reason);
    }, this.disconnectTimeout);
};

ReliableSocket.prototype._cancelDisconnect = function() {
    this.debug && console.log("cancel disconnect");
    clearTimeout(this._disconnectTimer);
};

ReliableSocket.prototype._delayedAck = function() {
    var that = this;
    this._cancelDelayedAck();
    this._ackTimer = setTimeout(function() {
        that._ack();
    }, this.ackTimeout);
};

ReliableSocket.prototype._cancelDelayedAck = function() {
    clearTimeout(this._ackTimer);
};

ReliableSocket.prototype._ack = function() {
    if (this.socket.readyState == "open") {
        this.debug && console.log("send ack", this.recId);
        this.socket.send(encodeURI(JSON.stringify({
            ack: this.recId
        })));
    }
};

ReliableSocket.prototype._flush = function() {
    if (this.socket.readyState == "open" && this.buffer.length) {
        var toSend = Math.min(
            this.congestionWindowSize - Object.keys(this.unacked).length,
            this.buffer.length
        );

        this.debug && console.log("flush", toSend, "messages");
        for (var i=0; i<toSend; i++) {
            var msg = this.buffer.shift();
            this._sendMessage(msg[0], msg[1]);
        }
        if (!this.buffer.length)
            this.emit("drain");
    }
};

ReliableSocket.prototype.disconnect = function(reason) {
    this.debug && console.log("disconnect");
    this.connected = false;
    this.recId = -1;
    this.duplicateCount = 0;
    this.missCount = 0;

    this.buffer = [];
    for (var key in this.unacked) {
        this.unacked[key].abort();
    }
    this.unacked = {};

    this.close();
    this.emit("away");

    var err = new Error("EDISCONNECT: " + (reason || "Client disconnected?"));
    err.code = "EDISCONNECT";
    this.emit("disconnect", err);
};

ReliableSocket.prototype.close = function() {
    return this.socket.close();
};

ReliableSocket.prototype.send = function(msg, type) {
    this._cancelDelayedAck();
    if (this.socket.readyState == "open" && Object.keys(this.unacked).length < this.congestionWindowSize) {
        this._sendMessage(msg, type);
        return true;
    }
    else {
        this.debug && console.log("buffer");
        this.buffer.push([msg, type]);
        return false;
    }
};

ReliableSocket.prototype._sendMessage = function(data, type) {
    var that = this;

    var msg = {
        seq: ++this.seq,
        ts: Date.now(),
        tries: 0,
        send: function() {
            msg.abort();
            that.unacked[msg.seq] = msg;
            msg.retransmissionTimer = setTimeout(function() {
                msg.tries += 1;
                that.debug && console.log("timeout", msg.seq);
                that._updateRetransmissionTimeout(0);
                that._updateCongestionWindowSize(true);

                msg.send();
            }, that.retransmissionTimeout);
            //that.debug && console.log("send", msg.serialize(), that.retransmissionTimeout);
            that.socket.send(msg.serialize());
        },
        abort: function() {
            delete that.unacked[msg.seq];
            clearTimeout(msg.retransmissionTimer);
        },
        acknowledge: function() {
            msg.abort();
            if (msg.tries > 0)
                that.debug && console.log("retry", msg.seq, that.congestionWindowSize, that.retransmissionTimeout);

            that._updateCongestionWindowSize(false);
            if (!msg.tries)
                that._updateRetransmissionTimeout(Date.now() - msg.ts);
        },
        serialize: function() {
            return escape(JSON.stringify({
                ack: that.recId,
                seq: msg.seq,
                d: data,
                t: type
            }));
        }
    };

    msg.send();
};

ReliableSocket.prototype._updateCongestionWindowSize = function(timeout) {
    if (timeout) {
        this.slowStartThreshold = Math.max(Math.floor(this.congestionWindowSize / 2), 50);
        this.congestionWindowSize = this.initialCongestionWindowSize;
    }
    else {
        if (this.congestionWindowSize > this.slowStartThreshold)
            this.congestionWindowSize += 1;
        else
            this.congestionWindowSize *= 2;
    }
};

// use TCP algorithm to compute the retransmission timeout
// http://de.wikipedia.org/wiki/Transmission_Control_Protocol#Retransmission_Timer
ReliableSocket.prototype._updateRetransmissionTimeout = function(rtt) {
    if (!rtt) {
        // there was a timeout
        this.retransmissionTimeout = Math.min(this.retransmissionTimeout * 2, this.maxRtt);
        this.srtt = null;
    }
    else if (!this.srtt) {
        this.srtt = rtt;
        this.rttVar = 0.5 * rtt;
        this.retransmissionTimeout = rtt * this.rttVar;
    }
    else {
        var alpha = 1/8;
        var beta = 1/4;
        this.srtt = (1-alpha) * this.srtt + alpha * rtt;
        this.rttVar = (1-beta) * this.rttVar + beta * (Math.abs(this.srtt - rtt));
        this.retransmissionTimeout = this.srtt + 4 * this.rttVar;
    }

    this.retransmissionTimeout = Math.min(Math.max(this.retransmissionTimeout, this.minRtt), this.maxRtt);
};

});<|endoftext|>