<commit_before>
// This file sets up boxing of javascript native objects into squeak objects
// All relevant classes are enhanced by that functionality and there are
// Several global functions to box or unbox objects.

// Some objects are boxing primitive js-values. Unboxing these values is polymorphic.
// The real version of this method is below. This could alternatively throw an error,
// but we try to be tolerant.
_Object._addInstanceMethods({
	_unbox: function() {
		return this;
	},
	// This never gets called, we just need a true-ish slot in each box-instance
	_isBoxedObject: function() { throw "just access this slot without calling."; }
});

// Class, that will wrap native javascript-objects to implement the Object-interface.
// Additionally, it adds accessor-methods for all fields to provide a squeak-API to
// set slots on javascript-objects.
Class("_Box", { superclass: _Object });

// Functions to bootstrap primitive values and wrap them into 'squeak'-objects
// Most functions are used in translated code directly, to avoid switch-statement in _boxObject()
// (Instead of bool(), compiled code uses _true/_false directly. bool() is used in kernel_primitives.js etc.)
var bool = function(aBool) { if (aBool) { return _true; } else { return _false; } }
var character = function(aString) { return Character._wrapping(aString); }
var string = function(aString) { return ByteString._wrapping(aString); }
var number = function(aNumber) { return Float._wrapping(aNumber); }
var array = function(anArray) { return _Array._wrapping(anArray); }
var object = function(anArray) { return _Box._wrapping(anArray); }
// if we box a javascript function into a smalltalk block we must bind it on creation
// Non-local-return handling is not required here
var boundBlock = function(func, that) {
	var b = BlockClosure._newInstance();
	b.original$ = function() {
		var unboxedArguments = [];
		for (index in arguments) {
			unboxedArguments.push(arguments[index].unbox());
		}
		return _boxObject(func.apply(that, unboxedArguments));
	}
	return b;
}

// There are multiple js-'classes', that will be wrapped into a Squeak-Array
var arrayConstructors = [Array, NodeList];
var isArrayObject = function(anObject) {
	var constructor = anObject.constructor;
	for (index in arrayConstructors) {
		if (arrayConstructors[index] == constructor) {
			return true;
		}
	}
	return false;
};

// This static unboxing-function is added to avoid adding an _unbox method to native js-objects
var _unboxObject = function(anyObject) {
	if (_boxObject._isBoxedObject) {
		return _boxObject._unbox();
	}
	return anyObject;
};
var _boxObject = function(nativeObject, that) { // The 'that' parameter is optional and will be undefined otherwise
	if (nativeObject._isBoxedObject) {
		switch( typeof(nativeObject) ) {
			case "number": return number(nativeObject); break;
			case "string": return string(nativeObject); break;
			case "boolean": return bool(nativeObject); break;
			case "function": return boundBlock(nativeObject, that); break;
			case "undefined": return nil; break;
			case "object":
				if (nativeObject == null) {
					return nil;
				} else if (isArrayObject(nativeObject)) {
					return array(nativeObject);
				} else {
					return object(nativeObject);
				}
				break;
			default:
				alert("Could not box creepy object: " + nativeObject);
		}
    }
	// nativeObject is already boxed!
	return nativeObject;
};

// These boxing classes box fixed, immutable values
False._addInstanceMethods( { _unbox: function() { return false; } } );
True._addInstanceMethods( { _unbox: function() { return true; } } );
UndefinedObject._addInstanceMethods( { _unbox: function() { return null; } } );

// These boxing classes box variable values and are all added the same functionality.
var boxingClasses = [_Box, ByteString, _Number, Character, BlockClosure, _Array, OrderedCollection];
var getAllPropertyNames = function(obj) {
	if (typeof(obj) == "object" && obj != null) {
		// recurse up the prototype chain until null
		return Object.getOwnPropertyNames(obj).concat(getAllPropertyNames(obj.__proto__));
	} else {
		return [];
	}
};
for (index in boxingClasses) {
	var aClass = boxingClasses[index];
	aClass._addClassMethods({
		_wrapping: function(originalObject) {
			var result = this._newInstance(); // Polymorphic for different kinds of boxes
			result.original$ = originalObject;
			return result;
		}
	});
	aClass._addInstanceMethods({
		_unbox: function() {
			return this.original$;
		}
	});
}
<commit_msg>further work on boxing<commit_after>
// This file sets up boxing of javascript native objects into squeak objects
// All relevant classes are enhanced by that functionality and there are
// Several global functions to box or unbox objects.

// Some objects are boxing primitive js-values. Unboxing these values is polymorphic.
// The real version of this method is below. This could alternatively throw an error,
// but we try to be tolerant.
_Object._addInstanceMethods({
	_unbox: function() {
		return this;
	},
	// This never gets called, we just need a true-ish slot in each box-instance
	_isBoxedObject: function() { throw "just access this slot without calling."; }
});

// Class, that will wrap native javascript-objects to implement the Object-interface.
// Additionally, it adds accessor-methods for all fields to provide a squeak-API to
// set slots on javascript-objects.
Class("_Box", { superclass: _Object });

// Functions to bootstrap primitive values and wrap them into 'squeak'-objects
// Most functions are used in translated code directly, to avoid switch-statement in _boxObject()
// (Instead of bool(), compiled code uses _true/_false directly. bool() is used in kernel_primitives.js etc.)
var bool = function(aBool) { if (aBool) { return _true; } else { return _false; } }
var character = function(aString) { return Character._wrapping(aString); }
var string = function(aString) { return ByteString._wrapping(aString); }
var number = function(aNumber) { return Float._wrapping(aNumber); }
var array = function(anArray) { return _Array._wrapping(anArray); }
var object = function(anObject) { return _Box._wrapping(anObject); }
// if we box a javascript function into a smalltalk block we must bind it on creation
// Non-local-return handling is not required here
var boundBlock = function(func, that) {
	var b = BlockClosure._newInstance();
	b.original$ = function() {
		var unboxedArguments = [];
		for (index in arguments) {
			unboxedArguments.push(arguments[index].unbox());
		}
		return _boxObject(func.apply(that, unboxedArguments));
	}
	return b;
}

// There are multiple js-'classes', that will be wrapped into a Squeak-Array
var arrayConstructors = [Array, NodeList];
var isArrayObject = function(anObject) {
	var constructor = anObject.constructor;
	for (index in arrayConstructors) {
		if (arrayConstructors[index] == constructor) {
			return true;
		}
	}
	return false;
};

// This static unboxing-function is added to avoid adding an _unbox method to native js-objects
var _unboxObject = function(anyObject) {
	if (_boxObject._isBoxedObject) {
		return _boxObject._unbox();
	}
	return anyObject;
};
var _boxObject = function(nativeObject, that) { // The 'that' parameter is optional and will be undefined otherwise
	if (nativeObject._isBoxedObject) {
		switch( typeof(nativeObject) ) {
			case "number": return number(nativeObject); break;
			case "string": return string(nativeObject); break;
			case "boolean": return bool(nativeObject); break;
			case "function": return boundBlock(nativeObject, that); break;
			case "undefined": return nil; break;
			case "object":
				if (nativeObject == null) {
					return nil;
				} else if (isArrayObject(nativeObject)) {
					return array(nativeObject);
				} else {
					return object(nativeObject);
				}
				break;
			default:
				alert("Could not box creepy object: " + nativeObject);
		}
    }
	// nativeObject is already boxed!
	return nativeObject;
};

// These boxing classes box fixed, immutable values
False._addInstanceMethods( { _unbox: function() { return false; } } );
True._addInstanceMethods( { _unbox: function() { return true; } } );
UndefinedObject._addInstanceMethods( { _unbox: function() { return null; } } );

// These boxing classes box variable values and are all added the same functionality.
var boxingClasses = [_Box, ByteString, _Number, Character, BlockClosure, _Array, OrderedCollection];
for (index in boxingClasses) {
	var aClass = boxingClasses[index];
	aClass._addClassMethods({
		_wrapping: function(originalObject) {
			var result = this._newInstance(); // Polymorphic for different kinds of boxes
			result.original$ = originalObject;
			return result;
		}
	});
	aClass._addInstanceMethods({
		doesNotUnderstand_: function(aMessage) {
			var methodName = aMessage.selector().unbox();
			if (methodName[methodName.length-1] == '_') {
				// setter
				this.original$[methodName.substring(0, methodName.length-1)] = aMessage.arguments().first()._unbox();
				return aMessage.arguments().first();
			} else {
				// getter
				return _Box._wrapping(this.original$[methodName]);
			}
		},
		_unbox: function() {
			return this.original$;
		}
	});
}
<|endoftext|>


<commit_before>var chokidar = require("chokidar");
var fs = require("fs");
var path = require("path");
var mkdirp = require("mkdirp");
// in order to grab up to date browserify
var browserify = require("../node_modules/watchify/node_modules/browserify/index.js");
var watchify = require("watchify");
var coffeeify = require("coffeeify");

var argv = require("optimist")
    .usage("Loops over a directory and Watchify's all files in that directory")

    .options("w", {
        alias: "watch",
        describe: "Watch the input file/directory and bind watchify to that directory"
    })

    .boolean(["w"])

    .argv;

function Directify(options) {
    options = options || {};

    this.argv = options.argv;
    this._run()
}

Directify.prototype._run = function() {
    var self = this;
    if(this.argv._.length < 2) {
        console.error("Input and output file/directory arguments required");
        process.exit(-1);
    }

    this.inputDir = this.argv._[0];
    this.outputDir = this.argv._[1];

    if(!fs.existsSync(this.inputDir)) {
        console.error("Input file does not exist:", this.inputDir);
        process.exit(1);
    }

    // ignoring .ds_store files
    var watcher = chokidar.watch(this.inputDir, { ignored: /\.DS_Store/ });

    watcher.on("add", function(inputPath) {
        self._addPath(inputPath);
    });
}

Directify.prototype._addPath = function(inputPath) {
    var self = this;
    var outputPath = this._replaceExtension(path.join(this.outputDir, path.relative(this.inputDir, inputPath)), ".coffee", ".js");

    var parentDirectoryPath = path.join(outputPath, "..");

    mkdirp(parentDirectoryPath, function(err) {
        if(err) {
            console.error("Could not create parent directory `" + parentDirectoryPath + "`:", err);
            return;
        }

        self._watchifyFile(inputPath, outputPath);
    });
}

Directify.prototype._replaceExtension = function(filepath, expectedExtension, newExtension) {
    var dirpath = path.dirname(filepath);
    var filename = path.basename(filepath, expectedExtension) + newExtension;
    return path.join(dirpath, filename);
}

Directify.prototype._watchifyFile = function(inputPath, outputPath) {
    console.log('input path: ' + inputPath);
    console.log('output path: ' + outputPath);
    var self = this;

    var b = browserify({
        cache: {},
        packageCache: {},
        fullPaths: true
    });
    b = watchify(b);
    b.on('update', function(){
        console.log(outputPath);
        self._bundleShare(b, outputPath);
    });

    b.add(inputPath);
    b.transform(coffeeify);
    console.log('applied transform');
    this._bundleShare(b, outputPath);
    return b;
}

Directify.prototype._bundleShare = function(b, outputPath) {
    console.log('bundling')
    b.bundle()
        .pipe(fs.createWriteStream(outputPath));
}

new Directify({argv: argv});

<commit_msg>removed console statements<commit_after>var chokidar = require("chokidar");
var fs = require("fs");
var path = require("path");
var mkdirp = require("mkdirp");
// in order to grab up to date browserify
var browserify = require("../node_modules/watchify/node_modules/browserify/index.js");
var watchify = require("watchify");
var coffeeify = require("coffeeify");

var argv = require("optimist")
    .usage("Loops over a directory and Watchify's all files in that directory")

    .options("w", {
        alias: "watch",
        describe: "Watch the input file/directory and bind watchify to that directory"
    })

    .boolean(["w"])

    .argv;

function Directify(options) {
    options = options || {};

    this.argv = options.argv;
    this._run()
}

Directify.prototype._run = function() {
    var self = this;

    if(this.argv._.length < 2) {
        console.error("Input and output file/directory arguments required");
        process.exit(-1);
    }

    this.inputDir = this.argv._[0];
    this.outputDir = this.argv._[1];

    if(!fs.existsSync(this.inputDir)) {
        console.error("Input file does not exist:", this.inputDir);
        process.exit(1);
    }

    // ignoring .ds_store files
    var watcher = chokidar.watch(this.inputDir, { ignored: /\.DS_Store/ });
    watcher.on("add", function(inputPath) {
        self._addPath(inputPath);
    });
}

Directify.prototype._addPath = function(inputPath) {
    var self = this;
    var outputPath = this._replaceExtension(path.join(this.outputDir, path.relative(this.inputDir, inputPath)), ".coffee", ".js");

    var parentDirectoryPath = path.join(outputPath, "..");

    mkdirp(parentDirectoryPath, function(err) {
        if(err) {
            console.error("Could not create parent directory `" + parentDirectoryPath + "`:", err);
            return;
        }

        self._watchifyFile(inputPath, outputPath);
    });
}

Directify.prototype._replaceExtension = function(filepath, expectedExtension, newExtension) {
    var dirpath = path.dirname(filepath);
    var filename = path.basename(filepath, expectedExtension) + newExtension;
    return path.join(dirpath, filename);
}

Directify.prototype._watchifyFile = function(inputPath, outputPath) {
    var b = browserify({
        cache: {},
        packageCache: {},
        fullPaths: true
    });
    b = watchify(b);
    b.on('update', this._bundleShare);

    b.add(inputPath);
    b.transform(coffeeify);
    this._bundleShare(b, outputPath);
    return b;
}

Directify.prototype._bundleShare = function(b, outputPath) {
    console.log('bundling')
    console.log(outputPath);
    b.bundle()
        .pipe(fs.createWriteStream(outputPath));
}

new Directify({argv: argv});
<|endoftext|>


<commit_before>function setupThrottledXhr(xhr, xhrProxy) {

    let {
        shaper
    } = xhrProxy;

    let openedTs, headersTs, loadingTs, doneTs;
    let loaded = 0, total;
    let currentBitrateKpbs;
    let progressEvents = [];
    let progressTimer;
    let lastProgressEvent = false;
    let loadEndEvent;
    let loadEvent;
    let done = false;

    xhr.onloadend = function(event) {

        let {
            _onloadend
        } = xhrProxy;

        //console.log('native loadend');
        loadEndEvent = event;
        if (done) {
            _onloadend && _onloadend(event);
            xhrProxy._dispatchWrappedEventType('loadend');
        }
    };

    xhr.onload = function(event) {

        let {
            _onload
        } = xhrProxy;

        //console.log('native load');
        loadEvent = event;
        if (done && xhr.readyState === 4) {
            xhrProxy._setupWrappedResponseData();
            _onload && _onload(event);
            xhrProxy._dispatchWrappedEventType('load');
        }
    };

    xhr.onreadystatechange = function(event) {

        let {
            _onreadystatechange,
            _onprogress,
            _onload,
            _onloadend
        } = xhrProxy;

        function triggerStateChange(e, readyState) {
            if (typeof readyState !== 'number') {
                throw new Error('readyState should be a number');
            }

            xhrProxy._readyState = readyState;
            _onreadystatechange && _onreadystatechange(e);
            xhrProxy._dispatchWrappedEventType('readystatechange');
        }

        switch (xhr.readyState) {
            case 0: // UNSENT
                triggerStateChange(event, 0);
                break;
            case 1: // OPENED
                openedTs = Date.now();
                triggerStateChange(event, 1);
                break;
            case 2: // HEADERS_RECEIVE
                headersTs = Date.now();
                xhrProxy._setupWrappedHeaders();
                triggerStateChange(event, 2);
                break;
            case 3: // LOADING
                loadingTs = Date.now();
                triggerStateChange(event, 3);
                break;
            case 4: // DONE
                let delay1 = 0, delay2 = 0;
                doneTs = Date.now();
                let latency = doneTs - openedTs;
                if (latency < shaper.minLatency) {
                    delay1 = shaper.minLatency - latency;
                }
                if (currentBitrateKpbs > shaper.maxBandwidth) {
                    delay2 = (currentBitrateKpbs / shaper.maxBandwidth) * latency - latency;
                }
                if (delay1 || delay2) {
                    setTimeout(function() {

                        if (loaded === total && !lastProgressEvent) {
                            clearTimeout(progressTimer);
                            _onprogress && _onprogress(progressEvents[progressEvents.length - 1]);
                            xhrProxy._dispatchWrappedEventType('progress');
                        }

                        triggerStateChange(event, 4);

                        done = true;

                        if (loadEvent) {
                            xhrProxy._setupWrappedResponseData();
                            _onload && _onload(loadEvent);
                            xhrProxy._dispatchWrappedEventType('load');
                            loadEvent = null;
                        }

                        if (loadEndEvent) {
                            _onloadend && _onloadend(loadEndEvent);
                            xhrProxy._dispatchWrappedEventType('loadend');
                            loadEndEvent = null;
                        }

                    }, Math.max(delay1, delay2));
                } else {
                    //console.log('done, not delaying');
                    done = true;
                    xhrProxy._setupWrappedResponseData();
                    triggerStateChange(event, 4);
                }
                break;
        }
    };

    xhr.onprogress = function(event) {

        let {
            _onprogress
        } = xhrProxy;

        function triggerProgress(e) {

            if (loaded === total) {
                lastProgressEvent = true;
            }

            _onprogress && _onprogress(e);
            xhrProxy._dispatchWrappedEventType('progress');
        }

        let now = Date.now();
        let duration = now - openedTs;
        let delay;

        loaded = event.loaded;
        total = event.total;
        currentBitrateKpbs = 8 * loaded / duration; // kbps

        // console.log('current bitrate: ' + Math.round(currentBitrateKpbs) + ' kbps');

        if (currentBitrateKpbs > shaper.maxBandwidth) {
            delay = (currentBitrateKpbs / shaper.maxBandwidth) * duration - duration;
            progressEvents.push(event);
            // console.log('delaying progress event by ' + Math.round(delay) + ' ms');
            progressTimer = setTimeout(function() {
                triggerProgress(event);
            }, delay);
            return;
        }

        triggerProgress(event);
    };

    let id = Math.round(Math.random() * 1e6);

    xhr.__throttledId = id;
    xhrProxy.__throttledId = id;
}

export default setupThrottledXhr;<commit_msg>add enum for XHR ready-states and clean up variable declarations<commit_after>var XHRReadyStates = {
    UNSENT: 0,
    OPENED: 1,
    HEADERS_RECEIVED: 2,
    LOADING: 3,
    DONE: 4
};

function setupThrottledXhr(xhr, xhrProxy) {

    let {
        shaper
    } = xhrProxy;

    let openedTs, headersTs, loadingTs, doneTs;
    let loaded = 0;
    let total = 0;
    let currentBitrateKpbs;
    let progressEvents = [];
    let progressTimer = null;
    let lastProgressEvent = false;
    let loadEndEvent = null;
    let loadEvent = null;
    let done = false;

    xhr.onloadend = function(event) {

        let {
            _onloadend
        } = xhrProxy;

        loadEndEvent = event;
        if (done) {
            _onloadend && _onloadend(event);
            xhrProxy._dispatchWrappedEventType('loadend');
        }
    };

    xhr.onload = function(event) {

        let {
            _onload
        } = xhrProxy;

        //console.log('native load');
        loadEvent = event;
        if (done && xhr.readyState === XHRReadyStates.DONE) {
            xhrProxy._setupWrappedResponseData();
            _onload && _onload(event);
            xhrProxy._dispatchWrappedEventType('load');
        }
    };

    xhr.onreadystatechange = function(event) {
        const now = Date.now();
        const {
            _onreadystatechange,
            _onprogress,
            _onload,
            _onloadend
        } = xhrProxy;
        const triggerStateChange = function(e, readyState) {
            if (typeof readyState !== 'number') {
                throw new Error('readyState should be a number');
            }

            xhrProxy._readyState = readyState;
            _onreadystatechange && _onreadystatechange(e);
            xhrProxy._dispatchWrappedEventType('readystatechange');
        }

        let latency;
        let delay1 = 0;
        let delay2 = 0;

        switch (xhr.readyState) {
            case 0: // UNSENT
                triggerStateChange(event, XHRReadyStates.UNSENT);
                break;
            case 1: // OPENED
                openedTs = now;
                triggerStateChange(event, XHRReadyStates.OPENED);
                break;
            case 2: // HEADERS_RECEIVED
                headersTs = now;
                xhrProxy._setupWrappedHeaders();
                triggerStateChange(event, XHRReadyStates.HEADERS_RECEIVED);
                break;
            case 3: // LOADING
                loadingTs = now;
                triggerStateChange(event, XHRReadyStates.LOADING);
                break;
            case 4: // DONE
                doneTs = now;
                latency = doneTs - openedTs;
                if (latency < shaper.minLatency) {
                    delay1 = shaper.minLatency - latency;
                }
                if (currentBitrateKpbs > shaper.maxBandwidth) {
                    delay2 = (currentBitrateKpbs / shaper.maxBandwidth) * latency - latency;
                }
                if (delay1 || delay2) {
                    setTimeout(function() {

                        if (loaded === total && !lastProgressEvent) {
                            clearTimeout(progressTimer);
                            _onprogress && _onprogress(progressEvents[progressEvents.length - 1]);
                            xhrProxy._dispatchWrappedEventType('progress');
                        }

                        triggerStateChange(event, XHRReadyStates.DONE);

                        done = true;

                        if (loadEvent) {
                            xhrProxy._setupWrappedResponseData();
                            _onload && _onload(loadEvent);
                            xhrProxy._dispatchWrappedEventType('load');
                            loadEvent = null;
                        }

                        if (loadEndEvent) {
                            _onloadend && _onloadend(loadEndEvent);
                            xhrProxy._dispatchWrappedEventType('loadend');
                            loadEndEvent = null;
                        }

                    }, Math.max(delay1, delay2));
                } else {
                    //console.log('done, not delaying');
                    done = true;
                    xhrProxy._setupWrappedResponseData();
                    triggerStateChange(event, XHRReadyStates.DONE);
                }
                break;
        }
    };

    xhr.onprogress = function(event) {
        const now = Date.now();
        const {
            _onprogress
        } = xhrProxy;
        const triggerProgress = function(e) {
            if (loaded === total) {
                lastProgressEvent = true;
            }

            _onprogress && _onprogress(e);
            xhrProxy._dispatchWrappedEventType('progress');
        }

        let duration = now - openedTs;
        let delay;

        loaded = event.loaded;
        total = event.total;
        currentBitrateKpbs = 8 * loaded / duration; // kbps

        if (currentBitrateKpbs > shaper.maxBandwidth) {
            delay = (currentBitrateKpbs / shaper.maxBandwidth) * duration - duration;
            progressEvents.push(event);
            progressTimer = setTimeout(function() {
                triggerProgress(event);
            }, delay);
            return;
        }

        triggerProgress(event);
    };
}

export default setupThrottledXhr;<|endoftext|>


<commit_before>import React from 'react'
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
import Html from '../containers/Html'
import { promisify } from 'bluebird'
import makeRoutes from '../routes'
import { configureStore } from '../store'
import { Provider } from 'react-redux'
import { match, RoutingContext } from 'react-router'
import createHistory from 'history/lib/createMemoryHistory'
import { syncReduxAndRouter } from 'redux-simple-router'
import { getPrefetchedData } from 'react-fetcher'
import { cyan, red } from 'chalk'
import { info, debug } from '../util/logging'
import { fetchCurrentUser, setMobileDevice } from '../actions'
import { localsForPrefetch } from '../util/universal'
import { getManifest } from '../util/assets'
import { some, isEmpty, toPairs } from 'lodash'
import { parse } from 'url'
import MobileDetect from 'mobile-detect'

const matchPromise = promisify(match, {multiArgs: true})

const checkAPIErrors = ({ errors }) => {
  return some(toPairs(errors), ([key, { payload: { response } }]) => {
    if (!response) return false
    let { status, url } = response
    debug(red(`${key} caused ${status} at ${url}`))
    return true
  })
}

const getPath = location => location.pathname + location.search

export default function (req, res) {
  info(cyan(`${req.method} ${req.originalUrl}`))

  const store = configureStore({}, req)
  const routes = makeRoutes(store)
  const history = createHistory()
  const md = new MobileDetect(req.headers['user-agent'])
  if (md.phone()) store.dispatch(setMobileDevice())

  return store.dispatch(fetchCurrentUser())
  .then(() => matchPromise({routes, location: req.originalUrl}))
  .then(([redirectLocation, renderProps]) => {
    if (redirectLocation) {
      return res.redirect(302, getPath(redirectLocation))
    }

    if (!renderProps) {
      return res.status(404).send('Not found')
    }

    return renderApp(res, renderProps, history, store)
    .then(app => {
      if (app.shouldRedirect) return res.redirect(302, app.shouldRedirect)

      const html = renderToStaticMarkup(app)
      res.status(200).send('<!DOCTYPE html>' + html)
    })
  })
  .catch(err => {
    res.errors = [err]
    res.setHeader('Content-Type', 'text/plain')
    const state = parse(req.url, true).query.verboseErrorPage
      ? JSON.stringify(store.getState(), null, 2)
      : ''
    res.status(500).send(`${err.stack}\n\n${state}`)
  })
}

function renderApp (res, renderProps, history, store) {
  const components = renderProps.routes.map(r => r.component)
  const locals = localsForPrefetch(renderProps, store)

  return getPrefetchedData(components, locals)
  .then(() => {
    const currentPath = getPath(renderProps.location)
    const state = store.getState()
    const { path } = state.routing

    // if this runs before getPrefetchedData it hangs -- infinite loop?
    history.transitionTo(renderProps.location)
    syncReduxAndRouter(history, store)

    // if navigate was called during prefetch, redirect.
    if (path && path !== currentPath) {
      return {shouldRedirect: path}
    }

    checkAPIErrors(state)
    if (!isEmpty(state.errors)) {
      res.errors = state.errors
    }

    const markup = renderToString(<Provider store={store}>
      <RoutingContext location='history' {...renderProps}/>
    </Provider>)

    return React.createElement(Html, {
      markup: markup,
      state: `window.INITIAL_STATE=${JSON.stringify(state).replace('</script>', '')}`,
      assetManifest: `window.ASSET_MANIFEST=${JSON.stringify(getManifest())}`,
      metaTags: state.metaTags
    })
  })
}
<commit_msg>use MobileDetect.mobile() instead of phone()<commit_after>import React from 'react'
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
import Html from '../containers/Html'
import { promisify } from 'bluebird'
import makeRoutes from '../routes'
import { configureStore } from '../store'
import { Provider } from 'react-redux'
import { match, RoutingContext } from 'react-router'
import createHistory from 'history/lib/createMemoryHistory'
import { syncReduxAndRouter } from 'redux-simple-router'
import { getPrefetchedData } from 'react-fetcher'
import { cyan, red } from 'chalk'
import { info, debug } from '../util/logging'
import { fetchCurrentUser, setMobileDevice } from '../actions'
import { localsForPrefetch } from '../util/universal'
import { getManifest } from '../util/assets'
import { some, isEmpty, toPairs } from 'lodash'
import { parse } from 'url'
import MobileDetect from 'mobile-detect'

const matchPromise = promisify(match, {multiArgs: true})

const checkAPIErrors = ({ errors }) => {
  return some(toPairs(errors), ([key, { payload: { response } }]) => {
    if (!response) return false
    let { status, url } = response
    debug(red(`${key} caused ${status} at ${url}`))
    return true
  })
}

const getPath = location => location.pathname + location.search

export default function (req, res) {
  info(cyan(`${req.method} ${req.originalUrl}`))

  const store = configureStore({}, req)
  const routes = makeRoutes(store)
  const history = createHistory()
  const md = new MobileDetect(req.headers['user-agent'])
  if (md.mobile()) store.dispatch(setMobileDevice())

  return store.dispatch(fetchCurrentUser())
  .then(() => matchPromise({routes, location: req.originalUrl}))
  .then(([redirectLocation, renderProps]) => {
    if (redirectLocation) {
      return res.redirect(302, getPath(redirectLocation))
    }

    if (!renderProps) {
      return res.status(404).send('Not found')
    }

    return renderApp(res, renderProps, history, store)
    .then(app => {
      if (app.shouldRedirect) return res.redirect(302, app.shouldRedirect)

      const html = renderToStaticMarkup(app)
      res.status(200).send('<!DOCTYPE html>' + html)
    })
  })
  .catch(err => {
    res.errors = [err]
    res.setHeader('Content-Type', 'text/plain')
    const state = parse(req.url, true).query.verboseErrorPage
      ? JSON.stringify(store.getState(), null, 2)
      : ''
    res.status(500).send(`${err.stack}\n\n${state}`)
  })
}

function renderApp (res, renderProps, history, store) {
  const components = renderProps.routes.map(r => r.component)
  const locals = localsForPrefetch(renderProps, store)

  return getPrefetchedData(components, locals)
  .then(() => {
    const currentPath = getPath(renderProps.location)
    const state = store.getState()
    const { path } = state.routing

    // if this runs before getPrefetchedData it hangs -- infinite loop?
    history.transitionTo(renderProps.location)
    syncReduxAndRouter(history, store)

    // if navigate was called during prefetch, redirect.
    if (path && path !== currentPath) {
      return {shouldRedirect: path}
    }

    checkAPIErrors(state)
    if (!isEmpty(state.errors)) {
      res.errors = state.errors
    }

    const markup = renderToString(<Provider store={store}>
      <RoutingContext location='history' {...renderProps}/>
    </Provider>)

    return React.createElement(Html, {
      markup: markup,
      state: `window.INITIAL_STATE=${JSON.stringify(state).replace('</script>', '')}`,
      assetManifest: `window.ASSET_MANIFEST=${JSON.stringify(getManifest())}`,
      metaTags: state.metaTags
    })
  })
}
<|endoftext|>


<commit_before>/* tokens */
export const TEXT = 'TEXT'
export const ERROR = 'ERROR'
export const OPEN = 'OPEN'
export const CLOSE = 'CLOSE'
export const SELF_CLOSING = 'SELF_CLOSING'

/* eslint-disable */

/* matches code name */
const RX_KEY = '[a-zA-Z][a-zA-Z0-9_-]*'

/* matches paramters */
const RX_PARAM =       RX_KEY + '=\\d+\\.\\d+' +    // floats
                 '|' + RX_KEY + '=\\d+' +           // ints
                 '|' + RX_KEY + '="[^\\]"]*"' +     // double-qouted strings
                 '|' + RX_KEY + '=\'[^\\]\']*\'' +  // single-qouted strings
                 '|' + RX_KEY                       // flag
const RX_PARAMS = '(?:(?:' + RX_PARAM + ')(?:(?!\\s+/?\\])\\s|))+'

/* matches all code token types, used for quickly
   finding potentia code tokens */
const RX_ENCLOSURE   = '\\[\\/?[a-zA-Z][^\\]]+\\]'
/* matches opening code tokens [row] */
const RX_OPEN        = '\\[(' + RX_KEY + ')(\\s' + RX_PARAMS + ')?\\]'
/* matches self-closing code tokens [row/] */
const RX_SELFCLOSING = '\\[(' + RX_KEY + ')(\\s' + RX_PARAMS + ')?\\s?\\/\\]'
/* matches close code tokens [/row] */
const RX_CLOSE       = '\\[\\/(' + RX_KEY + ')\\]'

/* case-insensitive regular expressions */
const rxParams      = new RegExp(RX_PARAMS.substring(0, RX_PARAMS.length - 1), 'ig')
const rxEnclosure   = new RegExp(RX_ENCLOSURE, 'i')
const rxOpen        = new RegExp(RX_OPEN, 'i')
const rxClose       = new RegExp(RX_CLOSE, 'i')
const rxSelfclosing = new RegExp(RX_SELFCLOSING, 'i')

/* eslint-enable */

/**
 * Get token type based on token-string.
 *
 * Note: assuming that this is not a TEXT token
 *
 * @returns {string} token type
 */
function getTokenType(str) {
  if (str[1] === '/') {
    return CLOSE
  }
  if (str[str.length - 2] === '/') {
    return SELF_CLOSING
  }
  return OPEN
}

/**
 * Casts input string to native types.
 *
 * @returns {mixed} mixed value
 */
function castValue(value) {
  value = value.replace(/(^['"]|['"]$)/g, '')
  if (/^\d+$/.test(value)) return +value
  if (/^\d+.\d+$/.test(value)) return parseFloat(value)
  if (/^(true|false|yes|no)$/i.test(value)) {
    value = value.toLowerCase()
    return value === 'true' || value === 'yes'
  }
  if (value === 'undefined') return typeof thisIsNotDefined
  if (value === 'null') return null
  return value
}

/**
 * Token class is used both as a token during tokenization/lexing
 * and as a node in the resulting AST.
 *
 * @private
 */
export class Token {
  constructor(type, body, pos = 0) {
    this.name = null
    this.type = type
    this.body = body
    this.pos = pos
    this.children = []
    this.params = {}
    this.isClosed = type === SELF_CLOSING
    this.init()
  }

  /**
   * @private
   */
  init() {
    if (this.type !== TEXT && this.type !== ERROR) {
      const match = this.matchBody()
      this.initName(match)
      if (match[2]) {
        this.initParams(match[2])
      }
    }
  }

  /**
   * @private
   */
  initName(match) {
    this.name = match[1]
  }

  /**
   * @private
   */
  initParams(paramStr) {
    const match = paramStr.match(rxParams)
    this.params = match.reduce((params, paramToken) => {
      paramToken = paramToken.trim()
      let equal = paramToken.indexOf('=')
      if (!~equal) {
        params[paramToken] = true
      } else {
        params[paramToken.substring(0, equal)] = castValue(paramToken.substring(equal + 1))
      }
      return params
    }, {})
  }

  /**
   * @private
   */
  matchBody() {
    let rx
    if (this.type === CLOSE) {
      rx = rxClose
    } else if (this.type === OPEN) {
      rx = rxOpen
    } else if (this.type === SELF_CLOSING) {
      rx = rxSelfclosing
    } else {
      throw new SyntaxError('Unknown token: ' + this.type)
    }

    let match = this.body.match(rx)
    if (match === null) {
      throw new SyntaxError('Invalid ' + this.type + ' token: ' + this.body)
    }
    return match
  }

  /**
   * Determines if this token can close the param token.
   *
   * @public
   * @returns {boolean}
   */
  canClose(token) {
    return this.name === token.name
  }
}

export default class ShortcodeTokenizer {

  /**
   * Creates a new tokenizer.
   *
   * Pass in input as first param or later using `input()`
   *
   * @param {string} Optional input to tokenize
   * @param {boolean} Strict mode default on
   */
  constructor(input = null, strict = true) {
    this.strict = strict
    this.buf = null
    this.originalBuf = null
    this.pos = 0
    if (input) {
      this.input(input)
    }
  }

  /**
   * Sets input buffer with a new input string.
   *
   * @throws {Error} Invalid input
   * @returns {this} returns this for chaining
   */
  input(input) {
    if (typeof input !== 'string') {
      throw new Error('Invalid input')
    }

    this.buf = this.originalBuf = input
    this.pos = 0
    return this
  }

  /**
   * Resets input buffer and position to their origial values.
   *
   * @returns {this} returns this for chaining
   */
  reset() {
    this.buf = this.originalBuf
    this.pos = 0
    return this
  }

  /**
   * Creates a token generator.
   *
   * @throws {Error} Invalid input
   * @returns {*function} token generator
   */
  *tokens(input = null) {
    if (input) {
      this.input(input)
    }

    if (typeof this.buf !== 'string') {
      throw new Error('Invalid input')
    }

    let tokens = []
    let token
    while ((tokens = this._next()) !== null) {
      tokens = Array.isArray(tokens) ? tokens : [tokens]
      for (token of tokens) {
        yield token
      }
    }
  }

  /**
   * Convenience function for getting all tokens.
   *
   * @see tokens
   * @returns {array} an array of tokens
   */
  getTokens(input = null) {
    return Array.from(this.tokens(input))
  }

  /**
   * Uses the tokens generator to build an AST from the tokens.
   *
   * @see tokens
   * @returns {array} an array of AST roots
   */
  ast(input = null) {
    let tokens = this.tokens(input)
    let stack = []
    let ast = []
    let parent = null
    let token
    for (token of tokens) {
      if (token.type === TEXT) {
        if (!parent) {
          ast.push(token)
        } else {
          parent.children.push(token)
        }
      } else if (token.type === OPEN) {
        if (!parent) {
          parent = token
          ast.push(parent)
        } else {
          parent.children.push(token)
          stack.push(parent)
          parent = token
        }
      } else if (token.type === CLOSE) {
        if (!parent || !token.canClose(parent)) {
          if (this.strict) {
            throw new SyntaxError('Unmatched close token: ' + token.body)
          } else {
            let err = new Token(ERROR, token.body)
            if (!parent) {
              ast.push(err)
            } else {
              parent.children.push(err)
            }
          }
        } else {
          parent.isClosed = true
          parent = stack.pop()
        }
      } else if (token.type === SELF_CLOSING) {
        if (!parent) {
          ast.push(token)
        } else {
          parent.children.push(token)
        }
      } else {
        throw new SyntaxError('Unknown token: ' + token.type)
      }
    }
    if (parent) {
      if (this.strict) {
        throw new SyntaxError('Unmatched open token: ' + parent.body)
      } else {
        ast.push(new Token(ERROR, token.body))
      }
    }
    return ast
  }

  /**
   * Internal function used to retrieve the next token from the current
   * position in the input buffer.
   *
   * @private
   * @returns {Token} returns the next Token from the input buffer
   */
  _next() {
    if (!this.buf) {
      return null
    }

    let match = this.buf.match(rxEnclosure)

    // all text
    if (match === null) {
      let token = new Token(TEXT, this.buf, this.pos)
      this.pos += this.buf.length
      this.buf = null
      return token
    }

    let tokens = []

    // first part is text
    if (match.index !== 0) {
      tokens.push(new Token(
        TEXT,
        this.buf.substring(0, match.index),
        this.pos
      ))
    }

    // matching token
    tokens.push(new Token(
      getTokenType(match[0]),
      match[0],
      this.pos + match.index
    ))

    // shorten buffer
    this.buf = this.buf.substring(match.index + match[0].length)
    this.pos += match.index + match[0].length
    if (this.buf.length === 0) {
      this.buf = null
    }
    return tokens
  }
}

Object.assign(ShortcodeTokenizer, {
  TEXT,
  ERROR,
  OPEN,
  CLOSE,
  SELF_CLOSING,
  rxParams,
  rxEnclosure,
  rxOpen,
  rxClose,
  rxSelfclosing
})
<commit_msg>Type constants are no longer exposed directly.<commit_after>/* tokens */
const TEXT = 'TEXT'
const ERROR = 'ERROR'
const OPEN = 'OPEN'
const CLOSE = 'CLOSE'
const SELF_CLOSING = 'SELF_CLOSING'

/* eslint-disable */

/* matches code name */
const RX_KEY = '[a-zA-Z][a-zA-Z0-9_-]*'

/* matches paramters */
const RX_PARAM =       RX_KEY + '=\\d+\\.\\d+' +    // floats
                 '|' + RX_KEY + '=\\d+' +           // ints
                 '|' + RX_KEY + '="[^\\]"]*"' +     // double-qouted strings
                 '|' + RX_KEY + '=\'[^\\]\']*\'' +  // single-qouted strings
                 '|' + RX_KEY                       // flags
const RX_PARAMS = '(?:(?:' + RX_PARAM + ')(?:(?!\\s+/?\\])\\s|))+'

/* matches all code token types, used for quickly
   finding potentia code tokens */
const RX_ENCLOSURE   = '\\[\\/?[a-zA-Z][^\\]]+\\]'
/* matches opening code tokens [row] */
const RX_OPEN        = '\\[(' + RX_KEY + ')(\\s' + RX_PARAMS + ')?\\]'
/* matches self-closing code tokens [row/] */
const RX_SELFCLOSING = '\\[(' + RX_KEY + ')(\\s' + RX_PARAMS + ')?\\s?\\/\\]'
/* matches close code tokens [/row] */
const RX_CLOSE       = '\\[\\/(' + RX_KEY + ')\\]'

/* case-insensitive regular expressions */
const rxParams      = new RegExp(RX_PARAMS.substring(0, RX_PARAMS.length - 1), 'ig')
const rxEnclosure   = new RegExp(RX_ENCLOSURE, 'i')
const rxOpen        = new RegExp(RX_OPEN, 'i')
const rxClose       = new RegExp(RX_CLOSE, 'i')
const rxSelfclosing = new RegExp(RX_SELFCLOSING, 'i')

/* eslint-enable */

/**
 * Get token type based on token-string.
 *
 * Note: assuming that this is not a TEXT token
 *
 * @returns {string} token type
 */
function getTokenType(str) {
  if (str[1] === '/') {
    return CLOSE
  }
  if (str[str.length - 2] === '/') {
    return SELF_CLOSING
  }
  return OPEN
}

/**
 * Casts input string to native types.
 *
 * @returns {mixed} mixed value
 */
function castValue(value) {
  value = value.replace(/(^['"]|['"]$)/g, '')
  if (/^\d+$/.test(value)) return +value
  if (/^\d+.\d+$/.test(value)) return parseFloat(value)
  if (/^(true|false|yes|no)$/i.test(value)) {
    value = value.toLowerCase()
    return value === 'true' || value === 'yes'
  }
  if (value === 'undefined') return typeof thisIsNotDefined
  if (value === 'null') return null
  return value
}

/**
 * Token class is used both as a token during tokenization/lexing
 * and as a node in the resulting AST.
 *
 * @private
 */
export class Token {
  constructor(type, body, pos = 0) {
    this.name = null
    this.type = type
    this.body = body
    this.pos = pos
    this.children = []
    this.params = {}
    this.isClosed = type === SELF_CLOSING
    this.init()
  }

  /**
   * @private
   */
  init() {
    if (this.type !== TEXT && this.type !== ERROR) {
      const match = this.matchBody()
      this.initName(match)
      if (match[2]) {
        this.initParams(match[2])
      }
    }
  }

  /**
   * @private
   */
  initName(match) {
    this.name = match[1]
  }

  /**
   * @private
   */
  initParams(paramStr) {
    const match = paramStr.match(rxParams)
    this.params = match.reduce((params, paramToken) => {
      paramToken = paramToken.trim()
      let equal = paramToken.indexOf('=')
      if (!~equal) {
        params[paramToken] = true
      } else {
        params[paramToken.substring(0, equal)] = castValue(paramToken.substring(equal + 1))
      }
      return params
    }, {})
  }

  /**
   * @private
   */
  matchBody() {
    let rx
    if (this.type === CLOSE) {
      rx = rxClose
    } else if (this.type === OPEN) {
      rx = rxOpen
    } else if (this.type === SELF_CLOSING) {
      rx = rxSelfclosing
    } else {
      throw new SyntaxError('Unknown token: ' + this.type)
    }

    let match = this.body.match(rx)
    if (match === null) {
      throw new SyntaxError('Invalid ' + this.type + ' token: ' + this.body)
    }
    return match
  }

  /**
   * Determines if this token can close the param token.
   *
   * @public
   * @returns {boolean}
   */
  canClose(token) {
    return this.name === token.name
  }
}

export default class ShortcodeTokenizer {

  /**
   * Creates a new tokenizer.
   *
   * Pass in input as first param or later using `input()`
   *
   * @param {string} Optional input to tokenize
   * @param {boolean} Strict mode default on
   */
  constructor(input = null, strict = true) {
    this.strict = strict
    this.buf = null
    this.originalBuf = null
    this.pos = 0
    if (input) {
      this.input(input)
    }
  }

  /**
   * Sets input buffer with a new input string.
   *
   * @throws {Error} Invalid input
   * @returns {this} returns this for chaining
   */
  input(input) {
    if (typeof input !== 'string') {
      throw new Error('Invalid input')
    }

    this.buf = this.originalBuf = input
    this.pos = 0
    return this
  }

  /**
   * Resets input buffer and position to their origial values.
   *
   * @returns {this} returns this for chaining
   */
  reset() {
    this.buf = this.originalBuf
    this.pos = 0
    return this
  }

  /**
   * Creates a token generator.
   *
   * @throws {Error} Invalid input
   * @returns {*function} token generator
   */
  *tokens(input = null) {
    if (input) {
      this.input(input)
    }

    if (typeof this.buf !== 'string') {
      throw new Error('Invalid input')
    }

    let tokens = []
    let token
    while ((tokens = this._next()) !== null) {
      tokens = Array.isArray(tokens) ? tokens : [tokens]
      for (token of tokens) {
        yield token
      }
    }
  }

  /**
   * Convenience function for getting all tokens.
   *
   * @see tokens
   * @returns {array} an array of tokens
   */
  getTokens(input = null) {
    return Array.from(this.tokens(input))
  }

  /**
   * Uses the tokens generator to build an AST from the tokens.
   *
   * @see tokens
   * @returns {array} an array of AST roots
   */
  ast(input = null) {
    let tokens = this.tokens(input)
    let stack = []
    let ast = []
    let parent = null
    let token
    for (token of tokens) {
      if (token.type === TEXT) {
        if (!parent) {
          ast.push(token)
        } else {
          parent.children.push(token)
        }
      } else if (token.type === OPEN) {
        if (!parent) {
          parent = token
          ast.push(parent)
        } else {
          parent.children.push(token)
          stack.push(parent)
          parent = token
        }
      } else if (token.type === CLOSE) {
        if (!parent || !token.canClose(parent)) {
          if (this.strict) {
            throw new SyntaxError('Unmatched close token: ' + token.body)
          } else {
            let err = new Token(ERROR, token.body)
            if (!parent) {
              ast.push(err)
            } else {
              parent.children.push(err)
            }
          }
        } else {
          parent.isClosed = true
          parent = stack.pop()
        }
      } else if (token.type === SELF_CLOSING) {
        if (!parent) {
          ast.push(token)
        } else {
          parent.children.push(token)
        }
      } else {
        throw new SyntaxError('Unknown token: ' + token.type)
      }
    }
    if (parent) {
      if (this.strict) {
        throw new SyntaxError('Unmatched open token: ' + parent.body)
      } else {
        ast.push(new Token(ERROR, token.body))
      }
    }
    return ast
  }

  /**
   * Internal function used to retrieve the next token from the current
   * position in the input buffer.
   *
   * @private
   * @returns {Token} returns the next Token from the input buffer
   */
  _next() {
    if (!this.buf) {
      return null
    }

    let match = this.buf.match(rxEnclosure)

    // all text
    if (match === null) {
      let token = new Token(TEXT, this.buf, this.pos)
      this.pos += this.buf.length
      this.buf = null
      return token
    }

    let tokens = []

    // first part is text
    if (match.index !== 0) {
      tokens.push(new Token(
        TEXT,
        this.buf.substring(0, match.index),
        this.pos
      ))
    }

    // matching token
    tokens.push(new Token(
      getTokenType(match[0]),
      match[0],
      this.pos + match.index
    ))

    // shorten buffer
    this.buf = this.buf.substring(match.index + match[0].length)
    this.pos += match.index + match[0].length
    if (this.buf.length === 0) {
      this.buf = null
    }
    return tokens
  }
}

Object.assign(ShortcodeTokenizer, {
  TEXT,
  ERROR,
  OPEN,
  CLOSE,
  SELF_CLOSING,
  rxParams,
  rxEnclosure,
  rxOpen,
  rxClose,
  rxSelfclosing
})
<|endoftext|>


<commit_before>/**
 * Static call-tracking script
 * (c) 2015 sipuni.com
 * version 1.1
 */
;(function(root, factory) {

    "use strict";
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else {
        root.sipuniCalltracking = factory();
    }

}(this, function() {

    "use strict";

    /**
     * Default parameters
     * Those parameters are overridden with user parameters passed during initialization
     */
    var default_options = {
        targets: ['.ct_phone'],
        default_value: null,
        callback: null,
        pattern: '+9 (999) 999-99-99',
        sources: {
            'organic':{'ref':/:\/\/(?:www\.)?(google|yandex|mail\.ru|search\.tut\.by|rambler|bing|yahoo)(?:\.(\w+))?/ig},
            'social':{'ref':/:\/\/[^\/]*(twitter|t\.co|facebook|linkedin|vk\.com|odnoklassniki)/ig},
            'email':{'utm_source':'email'},
            'adwords':{'dst': 'gclid='},
            'ydirect_utm':{'utm_source':'direct.yandex.ru'},
            'ydirect_openstat':{'dst': function(subject){
                                            var o = query.getRawParam(subject, '_openstat');
                                            return (o && a2b(o).indexOf('direct.yandex.ru')>-1);
                                       }}
        },
        cookie_key: 'sipunicts',
        cookie_ttl_days: 30*3
    };

    /**
     * querySelectorAll for all browsers
     */
    var select = document.querySelectorAll || function(selector) {
        var style = document.styleSheets[0] || document.createStyleSheet();
        style.addRule(selector, 'foo:bar');
        var all = document.all, resultSet = [];
        for (var i = 0, l = all.length; i < l; i++) {
            if (all[i].currentStyle.foo === 'bar') resultSet[resultSet.length] = all[i];
        }
        style.removeRule(0);
        return resultSet;

    };

    /**
     * Base64 decode for all browsers
     */
    var a2b = window.atob || function a2b(a) {
        var b, c, d, e = {}, f = 0, g = 0, h = "", i = String.fromCharCode, j = a.length;
        for (b = 0; 64 > b; b++) e["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(b)] = b;
        for (c = 0; j > c; c++) for (b = e[a.charAt(c)], f = (f << 6) + b, g += 6; g >= 8; ) ((d = 255 & f >>> (g -= 8)) || j - 2 > c) && (h += i(d));
        return h;
    };

    /**
     * Type detection methods
     */
    var type = {

        isArray: Array.isArray || function(o) {
            return (
                typeof o === 'object' && Object.prototype.toString.call(o) === '[object Array]'
                );
        },

        isDictionary: function(o) {
            return (
                typeof o === 'object' && Object.prototype.toString.call(o) === '[object Object]'
                );
        },

        isRegExp: function(o) {
            return (
                Object.prototype.toString.call(o) === '[object RegExp]'
                );
        },

        isFunction: function(o) {
            return (
                typeof o === 'function' && Object.prototype.toString.call(o) === '[object Function]'
                );
        }
    };

    /**
     * Query related methods
     */
    var query = {

        /**
         * Returns non decoded value of a query string parameter
         * @param url URL with query string, or just query string part
         * @param name query parameter name
         * @returns string a string with parameter value or null of no parameter exist
         */
        getRawParam: function(url, name){
            var regex = new RegExp(name+"=([^&]+)");
            var found = regex.exec(url);
            return (found !== null ? found[1] : null);
        },
        /**
         * Returns a URL-decoded value of query string parameter
         * @param url url with query string, or just query string part
         * @param name query parameter name
         * @returns string A string with parameter value or null of no parameter exist
         */
        getParam: function(url, name){
            var found = query.getRawParam(url, name);
            return (found !== null ? decodeURIComponent(found) : null);
        }
    };

    /**
     * Cookie related methods
     */
    var cookies = {

        /**
         * Returns a top level domain from a subdomain
         * @param domain string with domain or subdomain
         * @returns string top level domain or empty string for the localhost
         */
        topDomain: function(domain) {
            if (!domain) {
                return null;
            }
            var parts = domain.split('.');
            if (parts.length > 1) {
                var result = parts[ parts.length - 2 ] + '.' + parts[ parts.length - 1 ];
                if (result.indexOf(':') + 1){
                    return result.substr(0, result.length-5)
                }
                else {
                    return result
                }
            }
            return '';
        },

        /**
         * Sets a cookie
         * @param name cookie name
         * @param value cookie value
         * @param exdays cookie duration in days. put here null for the session only cookie
         */
        set: function (name, value, exdays) {
            var exdate = new Date();
            exdate.setDate(exdate.getDate() + exdays);
            var c_value = encodeURIComponent(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString());
            var top_domain = cookies.topDomain(window.location.hostname);
            var cookie = name + "=" + c_value + "; path=/" + "; domain=" + (top_domain ? '.'+top_domain : '');
            document.cookie = cookie;
        },

        /**
         * Gets a cookie value
         * @param name cookie name
         * @returns string A string with cookie value or null if no cookie exist
         */
        get: function (name){
            var val = document.cookie;
            var start = val.indexOf(" " + name + "=");
            if (start == -1) {
                start = val.indexOf(name + "=");
            }
            if (start == -1) {
                val = null;
            }else{
                start = val.indexOf("=", start) + 1;
                var end = val.indexOf(";", start);
                if (end == -1) {
                    end = val.length;
                }
                val = decodeURIComponent(val.substring(start, end));
            }
            return val;
        },

        /**
         * Deletes a cookie
         * @param name cookie name to delete
         */
        delete: function (name){
            cookies.set(name, null, -10);
        }

    };

    /**
     * Parameters manipulation methods
     */
    var dict = {

        /**
         * Updated one dictionary with the elements of another one.
         * @param dict_dst destination dictionary
         * @param dict_src source dictionary
         */
        update: function  (dict_dst, dict_src) {
            var is_array = type.isArray(dict_src);
            dict_dst = dict_dst || (is_array?[]:{});
            for (var prop in dict_src) {
                var v = dict_src[prop];
                if (type.isDictionary(v)) {
                    var d = dict_dst.hasOwnProperty(prop) ? dict_dst[prop] : null;
                    dict_dst[prop] = dict.update(d, v);
                } else {
                    dict_dst[prop] = v;
                }
            }
            return dict_dst;
        },

        /**
         * Merges two dictionaries and creates a new dictionary with results.
         * @param dict1 first dictionary
         * @param dict2 second dictionar
         * @returns object A new dictionary containing both dictionaries
         */
        merge: function(dict1, dict2){
            var result = {};
            dict.update(result, dict1);
            dict.update(result, dict2);
            return result;
        },

        /**
         * Returns dictionary keys
         * @param dict dictionary to search for
         * @returns Array An array with keys
         */
        keys: function(dictionary){
            var keys = [];
            for (var key in dictionary) {
                if (dictionary.hasOwnProperty(key)) {
                    keys.push(key);
                }
            }
            return keys;
        },

        /**
         * Returns a value from the dictionary or default value
         * @param dictionary
         * @param key
         * @param default_value
         * @returns a value of default_value
         */
        getVal: function(dictionary, key, default_value) {
            if (!dictionary || !dictionary.hasOwnProperty(key))
                return default_value;
            return dictionary[key];
        }

    };

    var actions = {

        place: function(targets, value) {
            for (var i = 0; i < targets.length; i++) {
                targets[i].innerHTML = value;
            }
        },

        getTargets: function(selectors) {
            return select.call(document, selectors);
        },

        execCallback: function(phone, src_url, dst_url, options) {
            var cb = dict.getVal(options, 'callback', null);
            if (cb && type.isFunction(cb)){
                return cb(phone, src_url, dst_url, options);
            }
            return null;
        }

    };

    /**
     * Finds a phone number matching conditions
     */
    var tracker = {

        find: function(sources, phones, src_url, dst_url){

            for (var i=0; i<phones.length; i++){
                var phone = phones[i];
                var src = phone['src'];

                if(sources.hasOwnProperty(src)){

                    if(tracker.match(sources[src], src_url, dst_url)){
                        return phone;
                    }
                }
            }

            return null;
        },

        match: function(source, src_url, dst_url){

            var subject=null;
            var condition = null;
            var keys = dict.keys(source);

            for(var i=0; i<keys.length; i++){

                var key = keys[i];

                if( key.indexOf('utm_')>-1 ){
                    subject = query.getParam(dst_url, key);
                    condition = source[key];
                }else if( key=='ref' ){
                    subject = src_url;
                    condition = source[key];
                }else if( key == 'dst' ){
                    subject = dst_url;
                    condition = source[key];
                }
            }

            if(!subject || !condition){
                return false;
            }

            return (
                (type.isRegExp(condition) && condition.exec(subject)) ||
                (typeof(condition)=='string' && subject.indexOf(condition)>-1) ||
                (type.isFunction(condition) && condition(subject))
            );
        },

        findBySrc: function(phones, src){
            for (var i=0; i<phones.length; i++){
                var phone = phones[i];
                if (phone.hasOwnProperty('src') && phone['src'] == src){
                    return phone;
                }
            }
            return null;
        }
    };

    /**
     * Phone number formatting support
     */
    var template = {

        /**
         * Formats the phone number, removes a plus sign for the 8-800 numbers.
         * @param string phone number
         * @param string pattern, 9 as a placeholder. E.g. +9 (999) 999-99-99
         * @returns {string} formatter phone number
         */
        format_number: function (num, pattern){

            if(num.toString().match(/^8800/) && pattern && pattern[0]=='+'){
                pattern = pattern.replace(/^\+/, '');
            }
            return template.format(num, pattern);
        },

        /**
         * Formats the phone number using the pattern
         * @param string phone number
         * @param string pattern, 9 as a placeholder. E.g. +9 (999) 999-99-99
         * @returns {string} formatter phone number
         */
        format: function (num, pattern){

            if(!(typeof(num) === "string")){
                num = num.toString();
            }

            if(!num)
                return '';

            if(typeof(pattern) === "undefined" || !pattern)
                return num;

            var arr_res=[];
            var num_counter = num.length-1;

            for(var i=pattern.length-1; i>=0; i--){
                var s = pattern[i];
                if(s=='9'){
                    if(num_counter>=0){
                        arr_res.push(num[num_counter--]);
                    }
                }
                else{
                    arr_res.push(s);
                }
            }
            return arr_res.reverse().join('');
        }
    };

    return function(user_options, wnd) {

        var options = dict.merge(default_options, user_options);
        var phone = null;
        var sources = options['sources'];
        var phones = options['phones'];
        var src_url = wnd.document.referrer;
        var dst_url = wnd.location.href;

        // find from cookies first
        var existing_src = cookies.get(options['cookie_key']);
        if(existing_src && sources.hasOwnProperty(existing_src)){

            phone = tracker.findBySrc(phones, existing_src);

        }

        // find from urls
        if( phone === null ){

            phone = tracker.find(
                sources,
                phones,
                src_url,
                dst_url);

            // store in cookies
            if(phone !== null){
                cookies.set(options['cookie_key'], phone['src'], options['cookie_ttl_days']);
            }
        }

        // use default
        if(phone === null){
            phone = options['default_phone'];
        }

        // insert phone numbers
        if (phone){
            var targets = options['targets'];
            var numbers = phone['phone'];
            for(var i=0, len=Math.min(targets.length, numbers.length); i<len; i++){
                var elements = actions.getTargets(targets[i]);
                var number = template.format_number(numbers[i], options['pattern']);
                actions.place(elements, number);
            }
        }

        // callback
        if(phone){
            actions.execCallback(phone, src_url, dst_url, options);
        }
    };

}));<commit_msg>Добавлен template в функцию обратного вызова<commit_after>/**
 * Static call-tracking script
 * (c) 2015 sipuni.com
 * version 1.1
 */
;(function(root, factory) {

    "use strict";
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else {
        root.sipuniCalltracking = factory();
    }

}(this, function() {

    "use strict";

    /**
     * Default parameters
     * Those parameters are overridden with user parameters passed during initialization
     */
    var default_options = {
        targets: ['.ct_phone'],
        default_value: null,
        callback: null,
        pattern: '+9 (999) 999-99-99',
        sources: {
            'organic':{'ref':/:\/\/(?:www\.)?(google|yandex|mail\.ru|search\.tut\.by|rambler|bing|yahoo)(?:\.(\w+))?/ig},
            'social':{'ref':/:\/\/[^\/]*(twitter|t\.co|facebook|linkedin|vk\.com|odnoklassniki)/ig},
            'email':{'utm_source':'email'},
            'adwords':{'dst': 'gclid='},
            'ydirect_utm':{'utm_source':'direct.yandex.ru'},
            'ydirect_openstat':{'dst': function(subject){
                                            var o = query.getRawParam(subject, '_openstat');
                                            return (o && a2b(o).indexOf('direct.yandex.ru')>-1);
                                       }}
        },
        cookie_key: 'sipunicts',
        cookie_ttl_days: 30*3
    };

    /**
     * querySelectorAll for all browsers
     */
    var select = document.querySelectorAll || function(selector) {
        var style = document.styleSheets[0] || document.createStyleSheet();
        style.addRule(selector, 'foo:bar');
        var all = document.all, resultSet = [];
        for (var i = 0, l = all.length; i < l; i++) {
            if (all[i].currentStyle.foo === 'bar') resultSet[resultSet.length] = all[i];
        }
        style.removeRule(0);
        return resultSet;

    };

    /**
     * Base64 decode for all browsers
     */
    var a2b = window.atob || function a2b(a) {
        var b, c, d, e = {}, f = 0, g = 0, h = "", i = String.fromCharCode, j = a.length;
        for (b = 0; 64 > b; b++) e["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(b)] = b;
        for (c = 0; j > c; c++) for (b = e[a.charAt(c)], f = (f << 6) + b, g += 6; g >= 8; ) ((d = 255 & f >>> (g -= 8)) || j - 2 > c) && (h += i(d));
        return h;
    };

    /**
     * Type detection methods
     */
    var type = {

        isArray: Array.isArray || function(o) {
            return (
                typeof o === 'object' && Object.prototype.toString.call(o) === '[object Array]'
                );
        },

        isDictionary: function(o) {
            return (
                typeof o === 'object' && Object.prototype.toString.call(o) === '[object Object]'
                );
        },

        isRegExp: function(o) {
            return (
                Object.prototype.toString.call(o) === '[object RegExp]'
                );
        },

        isFunction: function(o) {
            return (
                typeof o === 'function' && Object.prototype.toString.call(o) === '[object Function]'
                );
        }
    };

    /**
     * Query related methods
     */
    var query = {

        /**
         * Returns non decoded value of a query string parameter
         * @param url URL with query string, or just query string part
         * @param name query parameter name
         * @returns string a string with parameter value or null of no parameter exist
         */
        getRawParam: function(url, name){
            var regex = new RegExp(name+"=([^&]+)");
            var found = regex.exec(url);
            return (found !== null ? found[1] : null);
        },
        /**
         * Returns a URL-decoded value of query string parameter
         * @param url url with query string, or just query string part
         * @param name query parameter name
         * @returns string A string with parameter value or null of no parameter exist
         */
        getParam: function(url, name){
            var found = query.getRawParam(url, name);
            return (found !== null ? decodeURIComponent(found) : null);
        }
    };

    /**
     * Cookie related methods
     */
    var cookies = {

        /**
         * Returns a top level domain from a subdomain
         * @param domain string with domain or subdomain
         * @returns string top level domain or empty string for the localhost
         */
        topDomain: function(domain) {
            if (!domain) {
                return null;
            }
            var parts = domain.split('.');
            if (parts.length > 1) {
                var result = parts[ parts.length - 2 ] + '.' + parts[ parts.length - 1 ];
                if (result.indexOf(':') + 1){
                    return result.substr(0, result.length-5)
                }
                else {
                    return result
                }
            }
            return '';
        },

        /**
         * Sets a cookie
         * @param name cookie name
         * @param value cookie value
         * @param exdays cookie duration in days. put here null for the session only cookie
         */
        set: function (name, value, exdays) {
            var exdate = new Date();
            exdate.setDate(exdate.getDate() + exdays);
            var c_value = encodeURIComponent(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString());
            var top_domain = cookies.topDomain(window.location.hostname);
            var cookie = name + "=" + c_value + "; path=/" + "; domain=" + (top_domain ? '.'+top_domain : '');
            document.cookie = cookie;
        },

        /**
         * Gets a cookie value
         * @param name cookie name
         * @returns string A string with cookie value or null if no cookie exist
         */
        get: function (name){
            var val = document.cookie;
            var start = val.indexOf(" " + name + "=");
            if (start == -1) {
                start = val.indexOf(name + "=");
            }
            if (start == -1) {
                val = null;
            }else{
                start = val.indexOf("=", start) + 1;
                var end = val.indexOf(";", start);
                if (end == -1) {
                    end = val.length;
                }
                val = decodeURIComponent(val.substring(start, end));
            }
            return val;
        },

        /**
         * Deletes a cookie
         * @param name cookie name to delete
         */
        delete: function (name){
            cookies.set(name, null, -10);
        }

    };

    /**
     * Parameters manipulation methods
     */
    var dict = {

        /**
         * Updated one dictionary with the elements of another one.
         * @param dict_dst destination dictionary
         * @param dict_src source dictionary
         */
        update: function  (dict_dst, dict_src) {
            var is_array = type.isArray(dict_src);
            dict_dst = dict_dst || (is_array?[]:{});
            for (var prop in dict_src) {
                var v = dict_src[prop];
                if (type.isDictionary(v)) {
                    var d = dict_dst.hasOwnProperty(prop) ? dict_dst[prop] : null;
                    dict_dst[prop] = dict.update(d, v);
                } else {
                    dict_dst[prop] = v;
                }
            }
            return dict_dst;
        },

        /**
         * Merges two dictionaries and creates a new dictionary with results.
         * @param dict1 first dictionary
         * @param dict2 second dictionar
         * @returns object A new dictionary containing both dictionaries
         */
        merge: function(dict1, dict2){
            var result = {};
            dict.update(result, dict1);
            dict.update(result, dict2);
            return result;
        },

        /**
         * Returns dictionary keys
         * @param dict dictionary to search for
         * @returns Array An array with keys
         */
        keys: function(dictionary){
            var keys = [];
            for (var key in dictionary) {
                if (dictionary.hasOwnProperty(key)) {
                    keys.push(key);
                }
            }
            return keys;
        },

        /**
         * Returns a value from the dictionary or default value
         * @param dictionary
         * @param key
         * @param default_value
         * @returns a value of default_value
         */
        getVal: function(dictionary, key, default_value) {
            if (!dictionary || !dictionary.hasOwnProperty(key))
                return default_value;
            return dictionary[key];
        }

    };

    var actions = {

        place: function(targets, value) {
            for (var i = 0; i < targets.length; i++) {
                targets[i].innerHTML = value;
            }
        },

        getTargets: function(selectors) {
            return select.call(document, selectors);
        },

        execCallback: function(phone, src_url, dst_url, options) {
            var cb = dict.getVal(options, 'callback', null);
            if (cb && type.isFunction(cb)){
                return cb(phone, src_url, dst_url, options);
            }
            return null;
        }

    };

    /**
     * Finds a phone number matching conditions
     */
    var tracker = {

        find: function(sources, phones, src_url, dst_url){

            for (var i=0; i<phones.length; i++){
                var phone = phones[i];
                var src = phone['src'];

                if(sources.hasOwnProperty(src)){

                    if(tracker.match(sources[src], src_url, dst_url)){
                        return phone;
                    }
                }
            }

            return null;
        },

        match: function(source, src_url, dst_url){

            var subject=null;
            var condition = null;
            var keys = dict.keys(source);

            for(var i=0; i<keys.length; i++){

                var key = keys[i];

                if( key.indexOf('utm_')>-1 ){
                    subject = query.getParam(dst_url, key);
                    condition = source[key];
                }else if( key=='ref' ){
                    subject = src_url;
                    condition = source[key];
                }else if( key == 'dst' ){
                    subject = dst_url;
                    condition = source[key];
                }
            }

            if(!subject || !condition){
                return false;
            }

            return (
                (type.isRegExp(condition) && condition.exec(subject)) ||
                (typeof(condition)=='string' && subject.indexOf(condition)>-1) ||
                (type.isFunction(condition) && condition(subject))
            );
        },

        findBySrc: function(phones, src){
            for (var i=0; i<phones.length; i++){
                var phone = phones[i];
                if (phone.hasOwnProperty('src') && phone['src'] == src){
                    return phone;
                }
            }
            return null;
        }
    };

    /**
     * Phone number formatting support
     */
    var template = {

        /**
         * Formats the phone number, removes a plus sign for the 8-800 numbers.
         * @param string phone number
         * @param string pattern, 9 as a placeholder. E.g. +9 (999) 999-99-99
         * @returns {string} formatter phone number
         */
        format_number: function (num, pattern){

            if(num.toString().match(/^8800/) && pattern && pattern[0]=='+'){
                pattern = pattern.replace(/^\+/, '');
            }
            return template.format(num, pattern);
        },

        /**
         * Formats the phone number using the pattern
         * @param string phone number
         * @param string pattern, 9 as a placeholder. E.g. +9 (999) 999-99-99
         * @returns {string} formatter phone number
         */
        format: function (num, pattern){

            if(!(typeof(num) === "string")){
                num = num.toString();
            }

            if(!num)
                return '';

            if(typeof(pattern) === "undefined" || !pattern)
                return num;

            var arr_res=[];
            var num_counter = num.length-1;

            for(var i=pattern.length-1; i>=0; i--){
                var s = pattern[i];
                if(s=='9'){
                    if(num_counter>=0){
                        arr_res.push(num[num_counter--]);
                    }
                }
                else{
                    arr_res.push(s);
                }
            }
            return arr_res.reverse().join('');
        }
    };

    return function(user_options, wnd) {

        var options = dict.merge(default_options, user_options);
        var phone = null;
        var sources = options['sources'];
        var phones = options['phones'];
        var src_url = wnd.document.referrer;
        var dst_url = wnd.location.href;

        // find from cookies first
        var existing_src = cookies.get(options['cookie_key']);
        if(existing_src && sources.hasOwnProperty(existing_src)){

            phone = tracker.findBySrc(phones, existing_src);

        }

        // find from urls
        if( phone === null ){

            phone = tracker.find(
                sources,
                phones,
                src_url,
                dst_url);

            // store in cookies
            if(phone !== null){
                cookies.set(options['cookie_key'], phone['src'], options['cookie_ttl_days']);
            }
        }

        // use default
        if(phone === null){
            phone = options['default_phone'];
        }

        // insert phone numbers
        if (phone){
            var targets = options['targets'];
            var numbers = phone['phone'];
            for(var i=0, len=Math.min(targets.length, numbers.length); i<len; i++){
                var elements = actions.getTargets(targets[i]);
                var number = template.format_number(numbers[i], options['pattern']);
                actions.place(elements, number);
            }
        }

        // callback
        if(phone){
            actions.execCallback(phone, src_url, dst_url, options, template);
        }
    };

}));
<|endoftext|>