<commit_before>/**
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Dataset adapter utils.
 * @suppress {missingRequire}
 */

import {Environment, Navigator} from './environment.js';
import {getJSON} from './utils.js';

/**
 * Data adapter for the Matterport3D environment.
 */
export class Matterport3D {
  /**
   * Matterport3D constructor.
   * @param {string} path Directory or a url https://storage.googleapis.com/...
   */
  constructor(path) {
    /** @type {string} */
    this.path = path;
  }

  /**
   * Returns the raw connectivity entries from the Matteport3D simulator.
   * https://github.com/peteanderson80/Matterport3DSimulator/tree/master/connectivity
   * @param {string} scan Scan id.
   * @return {!Array<!Object>}
   */
  async getEntries(scan) {
    return await getJSON(`${this.path}/connectivity/${scan}_connectivity.json`);
  }

  /**
   * Returns the image urls from the Matterport3D dataset.
   * https://niessner.github.io/Matterport/
   * @param {!Object} entry
   * @param {string} scan Scan id.
   * @return {!Array<string>}
   */
  getUrls(entry, scan) {
    const dir = `${this.path}/data/v1/scans/${scan}/matterport_skybox_images`;
    const id = this.getId(entry);
    return [
      `${dir}/${id}_skybox2_sami.jpg`, `${dir}/${id}_skybox4_sami.jpg`,
      `${dir}/${id}_skybox0_sami.jpg`, `${dir}/${id}_skybox5_sami.jpg`,
      `${dir}/${id}_skybox1_sami.jpg`, `${dir}/${id}_skybox3_sami.jpg`
    ];
  }

  /**
   * Returns the panoramic viewpoint id.
   * @param {!Object} entry
   * @return {string}
   */
  getId(entry) {
    return entry['image_id'];
  }

  /**
   * Returns the extrinsic matrix of an entry.
   * @param {!Object} entry
   * @return {!THREE.Matrix4}
   */
  getExmat(entry) {
    const exmat = new THREE.Matrix4().fromArray(entry.pose);
    // Column to row major format.
    exmat.transpose();
    // Matterport3D to Three.js coordinates.
    exmat.premultiply(new THREE.Matrix4().makeRotationX(Math.PI / -2));
    exmat.scale(new THREE.Vector3(1, -1, -1));
    return exmat;
  }

  /**
   * Returns the cursor position slightly above floor-level.
   * @param {!Object} entry
   * @return {!THREE.Vector3}
   */
  getCurpos(entry) {
    const exmat = this.getExmat(entry);
    const curpos = new THREE.Vector3().setFromMatrixPosition(exmat);
    curpos.y -= (entry.height) ? entry.height - 0.5 : 0.3;
    return curpos;
  }

  /**
   * Returns the neighbors of an entry.
   * @param {!Object} entry
   * @param {!Array<!Object>} entries
   * @param {string} key Either 'unobstructed' or 'visible'.
   * @return {!Array<string>}
   */
  getNeighbors(entry, entries, key) {
    const neighbors = entry[key].flatMap((isNeighbor, i) => {
      const neighborEntry = entries[i];
      return (isNeighbor && neighborEntry.included) ?
          [this.getId(neighborEntry)] :
          [];
    });
    return (entry.included) ? neighbors : [];
  }

  /**
   * Returns an array of panoramas.
   * @param {string} scan Scan id.
   * @return {!Array<!Panorama>}
   */
  async getPanoramas(scan) {
    const entries = await this.getEntries(scan);
    return entries.flatMap((entry) => {
      const pano = {
        id: this.getId(entry),
        exmat: this.getExmat(entry),
        curpos: this.getCurpos(entry),
        urls: this.getUrls(entry, scan),
        navigable: this.getNeighbors(entry, entries, 'unobstructed'),
        visible: this.getNeighbors(entry, entries, 'visible')
      };
      return (entry.included) ? [pano] : [];
    });
  }

  /**
   * Returns a THREE.BoxGeometry for Matterport3D cubemap panoramas.
   * @return {!THREE.BoxGeometry}
   */
  getGeometry() {
    return new THREE.BoxGeometry(100, 100, 100);
  }

  /**
   * Returns a Matterport3D environment.
   * @param {string} scan Scan id.
   * @return {!Environment}
   */
  async getEnvironment(scan) {
    const panos = await this.getPanoramas(scan);
    const geometry = this.getGeometry();
    return new Environment(panos, geometry);
  }

  /**
   * Returns a Matterport3D navigator.
   * @param {string} scan Scan id.
   * @param {?HTMLCanvasElement} canvas
   * @return {!Navigator}
   */
  async getNavigator(scan, canvas) {
    const panos = await this.getPanoramas(scan);
    const geometry = this.getGeometry();
    return new Navigator(panos, geometry, canvas);
  }

  /**
   * Converts Matterport3D camera angles into Three.js rotations.
   * @param {number} heading Heading angle (y-rotation in Three.js).
   * @param {number} elevation Elevation angle (x-rotation in Three.js).
   * @return {!THREE.Euler}
   */
  convertRotation(heading, elevation) {
    return new THREE.Euler(elevation, -heading, 0, 'YXZ');
  }

  /**
   * Converts Three.js rotations into Matterport3D camera angles .
   * @param {!THREE.Euler} rotation Three.js camera rotation.
   * @return {!Array<number>} 2-tuple of [heading, pitch].
   */
  revertRotation(rotation) {
    const reordered = rotation.clone().reorder('YXZ');
    return [-reordered.y, reordered.x];
  }
}
<commit_msg>CrowdCompute Plugin Discoverability for PanGEA guide and follower plugins.<commit_after>/**
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Dataset adapter utils.
 * @suppress {missingRequire}
 */

import {Environment, Navigator} from './environment.js';
import {getJSON} from './utils.js';

/**
 * Data adapter for the Matterport3D environment.
 */
export class Matterport3D {
  /**
   * Matterport3D constructor.
   * @param {string} path Directory or a url https://storage.googleapis.com/...
   * @param {boolean=} flatten Whether the directory tree has been flattened
   *     with `path` as the root and no intermediate directories. For example:
   * `${path}/connectivity/${scan}_connectivity.json` becomes
   * `${path}/${scan}_connectivity.json`.
   */
  constructor(path, flatten = false) {
    /** @type {string} */
    this.path = path;
    /** @type {boolean} */
    this.flatten = flatten;
  }

  /**
   * Returns the raw connectivity entries from the Matteport3D simulator.
   * https://github.com/peteanderson80/Matterport3DSimulator/tree/master/connectivity
   * @param {string} scan Scan id.
   * @return {!Promise<!Array<!Object>>}
   */
  async getEntries(scan) {
    const dir = this.flatten ? this.path : `${this.path}/connectivity`;
    return await getJSON(`${dir}/${scan}_connectivity.json`);
  }

  /**
   * Returns the image urls from the Matterport3D dataset.
   * https://niessner.github.io/Matterport/
   * @param {!Object} entry
   * @param {string} scan Scan id.
   * @return {!Array<string>}
   */
  getUrls(entry, scan) {
    const dir = this.flatten ?
        this.path :
        `${this.path}/data/v1/scans/${scan}/matterport_skybox_images`;
    const id = this.getId(entry);
    return [
      `${dir}/${id}_skybox2_sami.jpg`, `${dir}/${id}_skybox4_sami.jpg`,
      `${dir}/${id}_skybox0_sami.jpg`, `${dir}/${id}_skybox5_sami.jpg`,
      `${dir}/${id}_skybox1_sami.jpg`, `${dir}/${id}_skybox3_sami.jpg`
    ];
  }

  /**
   * Returns the panoramic viewpoint id.
   * @param {!Object} entry
   * @return {string}
   */
  getId(entry) {
    return entry['image_id'];
  }

  /**
   * Returns the extrinsic matrix of an entry.
   * @param {!Object} entry
   * @return {!THREE.Matrix4}
   */
  getExmat(entry) {
    const exmat = new THREE.Matrix4().fromArray(entry.pose);
    // Column to row major format.
    exmat.transpose();
    // Matterport3D to Three.js coordinates.
    exmat.premultiply(new THREE.Matrix4().makeRotationX(Math.PI / -2));
    exmat.scale(new THREE.Vector3(1, -1, -1));
    return exmat;
  }

  /**
   * Returns the cursor position slightly above floor-level.
   * @param {!Object} entry
   * @return {!THREE.Vector3}
   */
  getCurpos(entry) {
    const exmat = this.getExmat(entry);
    const curpos = new THREE.Vector3().setFromMatrixPosition(exmat);
    curpos.y -= (entry.height) ? entry.height - 0.5 : 0.3;
    return curpos;
  }

  /**
   * Returns the neighbors of an entry.
   * @param {!Object} entry
   * @param {!Array<!Object>} entries
   * @param {string} key Either 'unobstructed' or 'visible'.
   * @return {!Array<string>}
   */
  getNeighbors(entry, entries, key) {
    const neighbors = entry[key].flatMap((isNeighbor, i) => {
      const neighborEntry = entries[i];
      return (isNeighbor && neighborEntry.included) ?
          [this.getId(neighborEntry)] :
          [];
    });
    return (entry.included) ? neighbors : [];
  }

  /**
   * Returns an array of panoramas.
   * @param {string} scan Scan id.
   * @return {!Array<!Panorama>}
   */
  async getPanoramas(scan) {
    const entries = await this.getEntries(scan);
    return entries.flatMap((entry) => {
      const pano = {
        id: this.getId(entry),
        exmat: this.getExmat(entry),
        curpos: this.getCurpos(entry),
        urls: this.getUrls(entry, scan),
        navigable: this.getNeighbors(entry, entries, 'unobstructed'),
        visible: this.getNeighbors(entry, entries, 'visible')
      };
      return (entry.included) ? [pano] : [];
    });
  }

  /**
   * Returns a THREE.BoxGeometry for Matterport3D cubemap panoramas.
   * @return {!THREE.BoxGeometry}
   */
  getGeometry() {
    return new THREE.BoxGeometry(100, 100, 100);
  }

  /**
   * Returns a Matterport3D environment.
   * @param {string} scan Scan id.
   * @return {!Environment}
   */
  async getEnvironment(scan) {
    const panos = await this.getPanoramas(scan);
    const geometry = this.getGeometry();
    return new Environment(panos, geometry);
  }

  /**
   * Returns a Matterport3D navigator.
   * @param {string} scan Scan id.
   * @param {?HTMLCanvasElement} canvas
   * @return {!Navigator}
   */
  async getNavigator(scan, canvas) {
    const panos = await this.getPanoramas(scan);
    const geometry = this.getGeometry();
    return new Navigator(panos, geometry, canvas);
  }

  /**
   * Converts Matterport3D camera angles into Three.js rotations.
   * @param {number} heading Heading angle (y-rotation in Three.js).
   * @param {number} elevation Elevation angle (x-rotation in Three.js).
   * @return {!THREE.Euler}
   */
  convertRotation(heading, elevation) {
    return new THREE.Euler(elevation, -heading, 0, 'YXZ');
  }

  /**
   * Converts Three.js rotations into Matterport3D camera angles .
   * @param {!THREE.Euler} rotation Three.js camera rotation.
   * @return {!Array<number>} 2-tuple of [heading, pitch].
   */
  revertRotation(rotation) {
    const reordered = rotation.clone().reorder('YXZ');
    return [-reordered.y, reordered.x];
  }
}
<|endoftext|>


<commit_before>	window.tempModeratorSkin=""
	if (ProLicenceUsersTable.ProLicenceUsers[window.agarioUID]){
		if (ProLicenceUsersTable.ProLicenceUsers[window.agarioUID].reason == "Moderator"){
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
		}
		if (ProLicenceUsersTable.ProLicenceUsers[window.agarioUID].reason == "LMauthor"){
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
		}
		if (ProLicenceUsersTable.ProLicenceUsers[window.agarioUID].reason == "Shiro"){
			window.tempModeratorSkin += '

'
		}
		if ($("#nick").val().includes('℄') || ProLicenceUsersTable.ProLicenceUsers[window.agarioUID].reason == "Admin"){
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'

		}
	}

            $('#helloContainer').after('
' +
				'<link rel="stylesheet" type="text/css" href="https://legendmod.ml/css/specialeffects.css">' +
				'

' +
                '
' +
                '
' +
                '
<button id="CloseLMShop" type="button" class="close" data-dismiss="modal">×' + Premadeletter113 + '</button> <button id="FAQLMShop" type="button" class="close" data-dismiss="modal">?' + Premadeletter113 + '</button>' +
                '
' + 'Legend mod Shop' + '
' +
                '
' +

				'
' +

        '
' +
    '
' +
        '
' +
            '
' +

                '
Premium
' +
				'
Free
' +
				'
In Use
' +
                '
<input type="text" id="skin-url" placeholder="Special Skin Effect" readonly>
' +
		'
Use
' +
		'
Stop
' +
		    '
' +
        '
' +
    '
' +

    '
' +
		'

' +
        '

' +
        '

' +
		'

' +
		'

' +
        '

' +
        '

' +
		'

' +
	    '

' +
	    '

' +
	    '

' +
	    '

' +
	    '

' +
	    '

' +
		'

' +
		window.tempModeratorSkin +
   '
' +
	        '
' +
	'
' +
        '
' +
	'
' +
	'
' +
    '' +
    '' +
                '
' +
                '
' +
                '
' +
                '
');

            $("#CloseLMShop").click(function() {
                $("#LMShop").remove();
            });
            $("#FAQLMShop").click(function() {
				window.open('https://legendmod.ml/', '_blank');
            });
<commit_msg>Update shop.js<commit_after>	window.tempModeratorSkin=""
	if (ProLicenceUsersTable.ProLicenceUsers[window.agarioUID]){
		if (ProLicenceUsersTable.ProLicenceUsers[window.agarioUID].reason == "Moderator"){
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
		}
		if (ProLicenceUsersTable.ProLicenceUsers[window.agarioUID].reason == "LMauthor"){
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
		}
		if (ProLicenceUsersTable.ProLicenceUsers[window.agarioUID].reason == "Shiro"){
			window.tempModeratorSkin += '

'
		}
		if ($("#nick").val().includes('℄') || ProLicenceUsersTable.ProLicenceUsers[window.agarioUID].reason == "Admin"){
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'
			window.tempModeratorSkin += '

'

		}
	}

            $('#helloContainer').after('
' +
				'<link rel="stylesheet" type="text/css" href="https://legendmod.ml/css/specialeffects.css">' +
				'

' +
                '
' +
                '
' +
                '
<button id="CloseLMShop" type="button" class="close" data-dismiss="modal">×' + Premadeletter113 + '</button> <button id="FAQLMShop" type="button" class="close" data-dismiss="modal">?' + Premadeletter113 + '</button>' +
                '
' + 'Legend mod Shop' + '
' +
                '
' +

				'
' +

        '
' +
    '
' +
        '
' +
            '
' +

                '
Premium
' +
				'
Free
' +
				'
In Use
' +
                '
<input type="text" id="skin-url" placeholder="Special Skin Effect" readonly>
' +
		'
Use
' +
		'
Stop
' +
		    '
' +
        '
' +
    '
' +

    '
' +
		'

' +
        '

' +
        '

' +
		'

' +
		'

' +
        '

' +
        '

' +
		'

' +
	    '

' +
	    '

' +
	    '

' +
	    '

' +
	    '

' +
	    '

' +
		'

' +
		window.tempModeratorSkin +
   '
' +
	        '
' +
	'
' +
        '
' +
	'
' +
	'
' +
    '' +
    '' +
                '
' +
                '
' +
                '
' +
                '
');

            $("#CloseLMShop").click(function() {
                $("#LMShop").remove();
            });
            $("#FAQLMShop").click(function() {
				window.open('https://legendmod.ml/', '_blank');
            });
<|endoftext|>


<commit_before>// @flow
import 'image-capture';
import './createImageBitmap';

import { getCurrentConference } from '../base/conference';
import { getLocalParticipant, getParticipantCount } from '../base/participants';
import { getLocalVideoTrack } from '../base/tracks';
import { getBaseUrl } from '../base/util';

import {
    ADD_FACE_EXPRESSION,
    ADD_TO_FACE_EXPRESSIONS_BUFFER,
    CLEAR_FACE_EXPRESSIONS_BUFFER,
    START_FACE_LANDMARKS_DETECTION,
    STOP_FACE_LANDMARKS_DETECTION,
    UPDATE_FACE_COORDINATES
} from './actionTypes';
import {
    DETECTION_TYPES,
    INIT_WORKER,
    WEBHOOK_SEND_TIME_INTERVAL
} from './constants';
import {
    getDetectionInterval,
    sendDataToWorker,
    sendFaceBoxToParticipants,
    sendFaceExpressionsWebhook
} from './functions';
import logger from './logger';

declare var APP: Object;

/**
 * Object containing  a image capture of the local track.
 */
let imageCapture;

/**
 * Object where the face landmarks worker is stored.
 */
let worker;

/**
 * The last face expression received from the worker.
 */
let lastFaceExpression;

/**
 * The last face expression timestamp.
 */
let lastFaceExpressionTimestamp;

/**
 * How many duplicate consecutive expression occurred.
 * If a expression that is not the same as the last one it is reset to 0.
 */
let duplicateConsecutiveExpressions = 0;

/**
 * Variable that keeps the interval for sending expressions to webhook.
 */
let webhookSendInterval;

/**
 * Variable that keeps the interval for detecting faces in a frame.
 */
let detectionInterval;

/**
 * Loads the worker that detects the face landmarks.
 *
 * @returns {void}
 */
export function loadWorker() {
    return function(dispatch: Function, getState: Function) {
        if (worker) {
            logger.info('Worker has already been initialized');

            return;
        }

        if (navigator.product === 'ReactNative') {
            logger.warn('Unsupported environment for face recognition');

            return;
        }

        const baseUrl = `${getBaseUrl()}libs/`;
        let workerUrl = `${baseUrl}face-landmarks-worker.min.js`;

        const workerBlob = new Blob([ `importScripts("${workerUrl}");` ], { type: 'application/javascript' });

        workerUrl = window.URL.createObjectURL(workerBlob);
        worker = new Worker(workerUrl, { name: 'Face Recognition Worker' });
        worker.onmessage = function(e: Object) {
            const { faceExpression, faceBox } = e.data;

            if (faceExpression) {
                if (faceExpression === lastFaceExpression) {
                    duplicateConsecutiveExpressions++;
                } else {
                    if (lastFaceExpression && lastFaceExpressionTimestamp) {
                        dispatch(addFaceExpression(
                            lastFaceExpression,
                            duplicateConsecutiveExpressions + 1,
                            lastFaceExpressionTimestamp
                        ));
                    }
                    lastFaceExpression = faceExpression;
                    lastFaceExpressionTimestamp = Date.now();
                    duplicateConsecutiveExpressions = 0;
                }
            }

            if (faceBox) {
                const state = getState();
                const conference = getCurrentConference(state);
                const localParticipant = getLocalParticipant(state);

                if (getParticipantCount(state) > 1) {
                    sendFaceBoxToParticipants(conference, faceBox);
                }

                dispatch({
                    type: UPDATE_FACE_COORDINATES,
                    faceBox,
                    id: localParticipant.id
                });
            }

            APP.API.notifyFaceLandmarkDetected(faceBox, faceExpression);
        };

        const { faceLandmarks } = getState()['features/base/config'];
        const detectionTypes = [
            faceLandmarks?.enableFaceCentering && DETECTION_TYPES.FACE_BOX,
            faceLandmarks?.enableFaceExpressionsDetection && DETECTION_TYPES.FACE_EXPRESSIONS
        ].filter(Boolean);

        worker.postMessage({
            type: INIT_WORKER,
            baseUrl,
            detectionTypes
        });

        dispatch(startFaceLandmarksDetection());
    };
}

/**
 * Starts the recognition and detection of face expressions.
 *
 * @param {Track | undefined} track - Track for which to start detecting faces.
 * @returns {Function}
 */
export function startFaceLandmarksDetection(track) {
    return async function(dispatch: Function, getState: Function) {
        if (!worker) {
            return;
        }

        const state = getState();
        const { recognitionActive } = state['features/face-landmarks'];

        if (recognitionActive) {
            logger.log('Face recognition already active.');

            return;
        }

        const localVideoTrack = track || getLocalVideoTrack(state['features/base/tracks']);

        if (localVideoTrack === undefined) {
            logger.warn('Face landmarks detection is disabled due to missing local track.');

            return;
        }

        const stream = localVideoTrack.jitsiTrack.getOriginalStream();

        dispatch({ type: START_FACE_LANDMARKS_DETECTION });
        logger.log('Start face recognition');

        const firstVideoTrack = stream.getVideoTracks()[0];
        const { faceLandmarks } = state['features/base/config'];

        imageCapture = new ImageCapture(firstVideoTrack);

        detectionInterval = setInterval(() => {
            sendDataToWorker(
                worker,
                imageCapture,
                faceLandmarks?.faceCenteringThreshold
            );
        }, getDetectionInterval(state));

        if (faceLandmarks?.enableFaceExpressionsDetection) {
            webhookSendInterval = setInterval(async () => {
                const result = await sendFaceExpressionsWebhook(getState());

                if (result) {
                    dispatch(clearFaceExpressionBuffer());
                }
            }, WEBHOOK_SEND_TIME_INTERVAL);
        }
    };
}

/**
 * Stops the recognition and detection of face expressions.
 *
 * @returns {void}
 */
export function stopFaceLandmarksDetection() {
    return function(dispatch: Function) {
        if (lastFaceExpression && lastFaceExpressionTimestamp) {
            dispatch(
                addFaceExpression(
                    lastFaceExpression,
                    duplicateConsecutiveExpressions + 1,
                    lastFaceExpressionTimestamp
                )
            );
        }

        clearInterval(webhookSendInterval);
        clearInterval(detectionInterval);

        duplicateConsecutiveExpressions = 0;
        webhookSendInterval = null;
        detectionInterval = null;
        imageCapture = null;

        dispatch({ type: STOP_FACE_LANDMARKS_DETECTION });
        logger.log('Stop face recognition');
    };
}

/**
 * Adds a new face expression and its duration.
 *
 * @param  {string} faceExpression - Face expression to be added.
 * @param  {number} duration - Duration in seconds of the face expression.
 * @param  {number} timestamp - Duration in seconds of the face expression.
 * @returns {Object}
 */
function addFaceExpression(faceExpression: string, duration: number, timestamp: number) {
    return function(dispatch: Function, getState: Function) {
        const finalDuration = duration * getDetectionInterval(getState()) / 1000;

        dispatch({
            type: ADD_FACE_EXPRESSION,
            faceExpression,
            duration: finalDuration,
            timestamp
        });
    };
}

/**
 * Adds a face expression with its timestamp to the face expression buffer.
 *
 * @param  {Object} faceExpression - Object containing face expression string and its timestamp.
 * @returns {Object}
 */
export function addToFaceExpressionsBuffer(faceExpression: Object) {
    return {
        type: ADD_TO_FACE_EXPRESSIONS_BUFFER,
        faceExpression
    };
}

/**
 * Clears the face expressions array in the state.
 *
 * @returns {Object}
 */
function clearFaceExpressionBuffer() {
    return {
        type: CLEAR_FACE_EXPRESSIONS_BUFFER
    };
}
<commit_msg>fix(face-landmarks): not sending duplicate expressions when recognition stops (#12067)<commit_after>// @flow
import 'image-capture';
import './createImageBitmap';

import { getCurrentConference } from '../base/conference';
import { getLocalParticipant, getParticipantCount } from '../base/participants';
import { getLocalVideoTrack } from '../base/tracks';
import { getBaseUrl } from '../base/util';

import {
    ADD_FACE_EXPRESSION,
    ADD_TO_FACE_EXPRESSIONS_BUFFER,
    CLEAR_FACE_EXPRESSIONS_BUFFER,
    START_FACE_LANDMARKS_DETECTION,
    STOP_FACE_LANDMARKS_DETECTION,
    UPDATE_FACE_COORDINATES
} from './actionTypes';
import {
    DETECTION_TYPES,
    INIT_WORKER,
    WEBHOOK_SEND_TIME_INTERVAL
} from './constants';
import {
    getDetectionInterval,
    sendDataToWorker,
    sendFaceBoxToParticipants,
    sendFaceExpressionsWebhook
} from './functions';
import logger from './logger';

declare var APP: Object;

/**
 * Object containing  a image capture of the local track.
 */
let imageCapture;

/**
 * Object where the face landmarks worker is stored.
 */
let worker;

/**
 * The last face expression received from the worker.
 */
let lastFaceExpression;

/**
 * The last face expression timestamp.
 */
let lastFaceExpressionTimestamp;

/**
 * How many duplicate consecutive expression occurred.
 * If a expression that is not the same as the last one it is reset to 0.
 */
let duplicateConsecutiveExpressions = 0;

/**
 * Variable that keeps the interval for sending expressions to webhook.
 */
let webhookSendInterval;

/**
 * Variable that keeps the interval for detecting faces in a frame.
 */
let detectionInterval;

/**
 * Loads the worker that detects the face landmarks.
 *
 * @returns {void}
 */
export function loadWorker() {
    return function(dispatch: Function, getState: Function) {
        if (worker) {
            logger.info('Worker has already been initialized');

            return;
        }

        if (navigator.product === 'ReactNative') {
            logger.warn('Unsupported environment for face recognition');

            return;
        }

        const baseUrl = `${getBaseUrl()}libs/`;
        let workerUrl = `${baseUrl}face-landmarks-worker.min.js`;

        const workerBlob = new Blob([ `importScripts("${workerUrl}");` ], { type: 'application/javascript' });

        workerUrl = window.URL.createObjectURL(workerBlob);
        worker = new Worker(workerUrl, { name: 'Face Recognition Worker' });
        worker.onmessage = function(e: Object) {
            const { faceExpression, faceBox } = e.data;

            if (faceExpression) {
                if (faceExpression === lastFaceExpression) {
                    duplicateConsecutiveExpressions++;
                } else {
                    if (lastFaceExpression && lastFaceExpressionTimestamp) {
                        dispatch(addFaceExpression(
                            lastFaceExpression,
                            duplicateConsecutiveExpressions + 1,
                            lastFaceExpressionTimestamp
                        ));
                    }
                    lastFaceExpression = faceExpression;
                    lastFaceExpressionTimestamp = Date.now();
                    duplicateConsecutiveExpressions = 0;
                }
            }

            if (faceBox) {
                const state = getState();
                const conference = getCurrentConference(state);
                const localParticipant = getLocalParticipant(state);

                if (getParticipantCount(state) > 1) {
                    sendFaceBoxToParticipants(conference, faceBox);
                }

                dispatch({
                    type: UPDATE_FACE_COORDINATES,
                    faceBox,
                    id: localParticipant.id
                });
            }

            APP.API.notifyFaceLandmarkDetected(faceBox, faceExpression);
        };

        const { faceLandmarks } = getState()['features/base/config'];
        const detectionTypes = [
            faceLandmarks?.enableFaceCentering && DETECTION_TYPES.FACE_BOX,
            faceLandmarks?.enableFaceExpressionsDetection && DETECTION_TYPES.FACE_EXPRESSIONS
        ].filter(Boolean);

        worker.postMessage({
            type: INIT_WORKER,
            baseUrl,
            detectionTypes
        });

        dispatch(startFaceLandmarksDetection());
    };
}

/**
 * Starts the recognition and detection of face expressions.
 *
 * @param {Track | undefined} track - Track for which to start detecting faces.
 * @returns {Function}
 */
export function startFaceLandmarksDetection(track) {
    return async function(dispatch: Function, getState: Function) {
        if (!worker) {
            return;
        }

        const state = getState();
        const { recognitionActive } = state['features/face-landmarks'];

        if (recognitionActive) {
            logger.log('Face recognition already active.');

            return;
        }

        const localVideoTrack = track || getLocalVideoTrack(state['features/base/tracks']);

        if (localVideoTrack === undefined) {
            logger.warn('Face landmarks detection is disabled due to missing local track.');

            return;
        }

        const stream = localVideoTrack.jitsiTrack.getOriginalStream();

        dispatch({ type: START_FACE_LANDMARKS_DETECTION });
        logger.log('Start face recognition');

        const firstVideoTrack = stream.getVideoTracks()[0];
        const { faceLandmarks } = state['features/base/config'];

        imageCapture = new ImageCapture(firstVideoTrack);

        detectionInterval = setInterval(() => {
            sendDataToWorker(
                worker,
                imageCapture,
                faceLandmarks?.faceCenteringThreshold
            );
        }, getDetectionInterval(state));

        if (faceLandmarks?.enableFaceExpressionsDetection) {
            webhookSendInterval = setInterval(async () => {
                const result = await sendFaceExpressionsWebhook(getState());

                if (result) {
                    dispatch(clearFaceExpressionBuffer());
                }
            }, WEBHOOK_SEND_TIME_INTERVAL);
        }
    };
}

/**
 * Stops the recognition and detection of face expressions.
 *
 * @returns {void}
 */
export function stopFaceLandmarksDetection() {
    return function(dispatch: Function, getState: Function) {
        const { recognitionActive } = getState()['features/face-landmarks'];

        if (lastFaceExpression && lastFaceExpressionTimestamp && recognitionActive) {
            dispatch(
                addFaceExpression(
                    lastFaceExpression,
                    duplicateConsecutiveExpressions + 1,
                    lastFaceExpressionTimestamp
                )
            );
        }

        clearInterval(webhookSendInterval);
        clearInterval(detectionInterval);

        duplicateConsecutiveExpressions = 0;
        lastFaceExpression = null;
        lastFaceExpressionTimestamp = null;
        webhookSendInterval = null;
        detectionInterval = null;
        imageCapture = null;

        dispatch({ type: STOP_FACE_LANDMARKS_DETECTION });
        logger.log('Stop face recognition');
    };
}

/**
 * Adds a new face expression and its duration.
 *
 * @param  {string} faceExpression - Face expression to be added.
 * @param  {number} duration - Duration in seconds of the face expression.
 * @param  {number} timestamp - Duration in seconds of the face expression.
 * @returns {Object}
 */
function addFaceExpression(faceExpression: string, duration: number, timestamp: number) {
    return function(dispatch: Function, getState: Function) {
        const finalDuration = duration * getDetectionInterval(getState()) / 1000;

        dispatch({
            type: ADD_FACE_EXPRESSION,
            faceExpression,
            duration: finalDuration,
            timestamp
        });
    };
}

/**
 * Adds a face expression with its timestamp to the face expression buffer.
 *
 * @param  {Object} faceExpression - Object containing face expression string and its timestamp.
 * @returns {Object}
 */
export function addToFaceExpressionsBuffer(faceExpression: Object) {
    return {
        type: ADD_TO_FACE_EXPRESSIONS_BUFFER,
        faceExpression
    };
}

/**
 * Clears the face expressions array in the state.
 *
 * @returns {Object}
 */
function clearFaceExpressionBuffer() {
    return {
        type: CLEAR_FACE_EXPRESSIONS_BUFFER
    };
}
<|endoftext|>


<commit_before>/*!
 * clockin <https://github.com/akileez/clockin>
 *
 * Copyright (c) 2015-2016 Keith Williams.
 * Licensed under the ISC license.
 */

var frmt = {
  'default'             : 'ddd mmm dd yyyy HH:MM:SS',
  'now'                 : 'dddd, mmmm o, yyyy HH:MM:SS \'GMT\'Z',
  'shortDate'           : 'm/d/yy',
  'mediumDate'          : 'mmm d, yyyy',
  'longDate'            : 'mmmm d, yyyy',
  'fullDate'            : 'dddd, mmmm d, yyyy',
  'shortTime'           : 'h:MM TT',
  'mediumTime'          : 'h:MM:SS TT',
  'longTime'            : 'h:MM:SS TT Z',
  'isoDate'             : 'yyyy-mm-dd',
  'isoTime'             : 'HH:MM:SS',
  'iso'                 : 'yyyy-mm-dd\'T\'HH:MM:SSZ',
  'isoDateTime'         : 'yyyy-mm-dd\'T\'HH:MM:SS:ss',
  'isoUtcDateTime'      : 'yyyy-mm-dd\'T\'HH:MM:SS\'Z\'',
  'expiresHeaderFormat' : 'ddd, dd mmm yyyy HH:MM:SS Z',
  'timestamp'           : 'yyyymmddHHMMSSss',
  'logStamp'            : 'HH:MM:SS:ss'
}

var dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']

var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']

function clockin (date, mask) {
  if (arguments.length === 1 && typeof date === 'string' && !/\d/.test(date)) {
    mask = date
    date = undefined
  }
  // console.log(/\d/.test(date))
  date = date || new Date
  // console.log('date: ', date)
  if (date instanceof Date) date = new Date(date)
  else date = new Date(Date.parse(date))
  if (isNaN(date)) throw TypeError('Invalid date')

  // what we wish to match
  var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMSsTt])\1?|[oZWQNv]|O{1,2}|u{1,2}|x{1,3}|'[^']*'/g
  // what we don't wish to match -- need to optimize!!
  var antiToken = /[AaBbCcDEeFfGgIiJjKkLlnPpqRrUVwXYz0-9\?\.\,\!@#\$%\^\&\*\(\)\+\-]/g

  // what mask to use
  mask = String(frmt[mask] || antiPattern(mask, antiToken) || frmt.default)

  function makeIterator (fn) {
    return function (mask, token) {
      return fn(date, mask)
    }
  }

  return mask.replace(token, makeIterator(convertFlag))

  function convertFlag (date, token) {
    var tokens = {
      d     : () => date.getDate(),
      dd    : () => pad(date.getDate(), 2),
      ddd   : () => nameOfDay(date, 3),
      dddd  : () => nameOfDay(date),
      m     : () => date.getMonth() + 1,
      mm    : () => pad(date.getMonth() + 1, 2),
      mmm   : () => nameOfMonth(date, 3),
      mmmm  : () => nameOfMonth(date),
      yyyy  : () => pad(date.getFullYear(), 4),
      yy    : () => pad(date.getFullYear() % 100, 2),
      h     : () => date.getHours() % 12 || 12,
      hh    : () => pad(date.getHours() % 12 || 12, 2),
      H     : () => date.getHours(),
      HH    : () => pad(date.getHours(), 2),
      M     : () => date.getMinutes(),
      MM    : () => pad(date.getMinutes(), 2),
      S     : () => date.getSeconds(),
      SS    : () => pad(date.getSeconds(), 2),
      s     : () => date.getMilliseconds(),
      ss    : () => pad(date.getMilliseconds(), 3),
      t     : () => date.getHours() < 12 ? 'a' : 'p',
      tt    : () => date.getHours() < 12 ? 'am' : 'pm',
      T     : () => date.getHours() < 12 ? 'A' : 'P',
      TT    : () => date.getHours() < 12 ? 'AM' : 'PM',
      N     : () => dayOfTheYear(date),
      W     : () => weekOfTheYear(date),
      Q     : () => quarterOfTheYear(date),
      Z     : () => timezoneOffset(date),
      u     : () => totalDaysThisMonth(date),
      uu    : () => totalDaysThisYear(date),
      x     : () => daysLeftThisWeek(date),
      xx    : () => daysLeftThisMonth(date),
      xxx   : () => daysLeftThisYear(date),
      v     : () => isLeapYear(date),
      o     : () => ordinal(date.getDate()),
      O     : () => nameOfDay(date, 1),
      OO    : () => nameOfDay(date, 2),
      defaults : () => token.replace(/'/g, '')
    }
    return typeof tokens[token] !== 'function'
      ? tokens.defaults()
      : tokens[token]()
  }

  function antiPattern (mask, token) {
    if (!token.test(mask)) return mask
    return false
  }

  function nameOfDay (date, n) {
    var thisday = date.getDay()
    var name = dayNames.filter(dayOfWeek).toString()

    function dayOfWeek (val, idx, arr) {
      if (idx === thisday) return val
    }

    if (n == null) return name
    else return abbreviateDayName(name, n)

    function abbreviateDayName (name, num) {
      if (num > 3) {
        if (name === 'Tuesday') return name.slice(0, 4)
        else if (name === 'Thursday' && num < 6) return name.slice(0, num)
        else return name.slice(0, 3)
      }
      return name.slice(0, num)
    }
  }

  function nameOfMonth (date, n) {
    var thismonth = date.getMonth()
    var name = monthNames.filter(monthOfYear).toString()

    function monthOfYear (val, idx, arr) {
      if (idx === thismonth) return val
    }

    if (n == null) return name
    else return abbreviateName(name, n)

    function abbreviateName (name, num) {
      if (num > 3) num = 3
      return name.slice(0, num)
    }
  }

  function dayOfTheYear (date) {
    // var now = new Date(date.getTime())
    // var then = new Date(date.getFullYear(), 0, 1)

    var now = date.getTime()
    var then = new Date(date.getFullYear(), 0, 1)

    return Math.ceil((now - then) / 86400000)
  }

  function weekOfTheYear () {
    // Remove time components of date
    var targetThursday = new Date(date.getFullYear(), date.getMonth(), date.getDate())

    // Change date to Thursday same week
    targetThursday.setDate(targetThursday.getDate() - ((targetThursday.getDay() + 6) % 7) + 3)

    // Take January 4th as it is always in week 1 (see ISO 8601)
    var firstThursday = new Date(targetThursday.getFullYear(), 0, 4)

    // Change date to Thursday same week
    firstThursday.setDate(firstThursday.getDate() - ((firstThursday.getDay() + 6) % 7) + 3)

    // Check if daylight-saving-time-switch occured and correct for it
    var ds = targetThursday.getTimezoneOffset() - firstThursday.getTimezoneOffset()
    targetThursday.setHours(targetThursday.getHours() - ds)

    // Number of weeks between target Thursday and first Thursday
    var weekDiff = (targetThursday - firstThursday) / (86400000*7)
    return 1 + Math.floor(weekDiff)
  }

  function quarterOfTheYear (date) {
    var month = date.getMonth() + 1
    return (Math.ceil(month / 3))
  }

  function timezoneOffset (date) {
    var offset = date.getTimezoneOffset()
    var abs = Math.abs(offset)
    var hr = pad(Math.floor(abs / 60), 2)
    var min = pad(abs % 60, 2)
    return [(offset > 0 ? '-' : '+'), hr, min].join('')
  }

  function totalDaysThisMonth (date) {
    var year = date.getFullYear()
    var month = date.getMonth() + 1

    return new Date(year, month, 0).getDate()
  }

  function totalDaysThisYear (date) {
    var thisYear = new Date(date).getFullYear()
    return isLeapYear(thisYear) ? 366 : 365
  }

  function isLeapYear (date) {
    var thisYear = isDate(date)
      ? new Date(date).getFullYear()
      : new Date().getFullYear()

    return thisYear % 400 === 0 || (thisYear % 100 !== 0 && thisYear % 4 === 0)
  }

  function daysLeftThisWeek (date) {
    return 6 - date.getDay()
  }

  function daysLeftThisMonth (date) {
    return totalDaysThisMonth(date) - date.getDate()
  }

  function daysLeftThisYear (date) {
    return totalDaysThisYear(date) - dayOfTheYear(date)
  }

  function ordinal (n) {
    n = toInt(n)
    return n + nth(n)
  }

  function nth (i) {
    return ['th', 'st', 'nd', 'rd'][i % 10 > 3 ? 0 : (i % 100 - i % 10 !== 10) * i % 10]
  }

  function toInt (val) {
    return ~~val
  }

  function isDate (value) {
    return Object.prototype.toString.call(value) === '[object Date]'
  }

  function pad (val, len) {
    val = String(val)
    len = len || 2
    while (val.length < len) {
      val = '0' + val
    }
    return val
  }
}

module.exports = clockin
<commit_msg>update date/clockin — alter iso date stamp for web<commit_after>/*!
 * clockin <https://github.com/akileez/clockin>
 *
 * Copyright (c) 2015-2016 Keith Williams.
 * Licensed under the ISC license.
 */

var frmt = {
  'default'             : 'ddd mmm dd yyyy HH:MM:SS',
  'now'                 : 'dddd, mmmm o, yyyy HH:MM:SS \'GMT\'Z',
  'shortDate'           : 'm/d/yy',
  'mediumDate'          : 'mmm d, yyyy',
  'longDate'            : 'mmmm d, yyyy',
  'fullDate'            : 'dddd, mmmm d, yyyy',
  'shortTime'           : 'h:MM TT',
  'mediumTime'          : 'h:MM:SS TT',
  'longTime'            : 'h:MM:SS TT Z',
  'isoDate'             : 'yyyy-mm-dd',
  'isoTime'             : 'HH:MM:SS',
  'iso'                 : 'yyyy-mm-dd\'T\'HH:MM:SSZ',
  'isoDateTime'         : 'yyyy-mm-dd\'T\'HH:MM:SS:ss',
  'isoUtcDateTime'      : 'yyyy-mm-dd\'T\'HH:MM:SS\'Z\'',
  'expiresHeaderFormat' : 'ddd, dd mmm yyyy HH:MM:SS Z',
  'timestamp'           : 'yyyymmddHHMMSSss',
  'logStamp'            : 'HH:MM:SS:ss'
}

var dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']

var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']

function clockin (date, mask) {
  if (arguments.length === 1 && typeof date === 'string' && !/\d/.test(date)) {
    mask = date
    date = undefined
  }
  // console.log(/\d/.test(date))
  date = date || new Date
  // console.log('date: ', date)
  if (date instanceof Date) date = new Date(date)
  else date = new Date(Date.parse(date))
  if (isNaN(date)) throw TypeError('Invalid date')

  // what we wish to match
  var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMSsTt])\1?|[oZWQNv]|O{1,2}|u{1,2}|x{1,3}|'[^']*'/g
  // what we don't wish to match -- need to optimize!!
  var antiToken = /[AaBbCcDEeFfGgIiJjKkLlnPpqRrUVwXYz0-9\?\.\,\!@#\$%\^\&\*\(\)\+\-]/g

  // what mask to use
  mask = String(frmt[mask] || antiPattern(mask, antiToken) || frmt.default)

  function makeIterator (fn) {
    return function (mask, token) {
      return fn(date, mask)
    }
  }

  return mask.replace(token, makeIterator(convertFlag))

  function convertFlag (date, token) {
    var tokens = {
      d     : () => date.getDate(),
      dd    : () => pad(date.getDate(), 2),
      ddd   : () => nameOfDay(date, 3),
      dddd  : () => nameOfDay(date),
      m     : () => date.getMonth() + 1,
      mm    : () => pad(date.getMonth() + 1, 2),
      mmm   : () => nameOfMonth(date, 3),
      mmmm  : () => nameOfMonth(date),
      yyyy  : () => pad(date.getFullYear(), 4),
      yy    : () => pad(date.getFullYear() % 100, 2),
      h     : () => date.getHours() % 12 || 12,
      hh    : () => pad(date.getHours() % 12 || 12, 2),
      H     : () => date.getHours(),
      HH    : () => pad(date.getHours(), 2),
      M     : () => date.getMinutes(),
      MM    : () => pad(date.getMinutes(), 2),
      S     : () => date.getSeconds(),
      SS    : () => pad(date.getSeconds(), 2),
      s     : () => date.getMilliseconds(),
      ss    : () => pad(date.getMilliseconds(), 3),
      t     : () => date.getHours() < 12 ? 'a' : 'p',
      tt    : () => date.getHours() < 12 ? 'am' : 'pm',
      T     : () => date.getHours() < 12 ? 'A' : 'P',
      TT    : () => date.getHours() < 12 ? 'AM' : 'PM',
      N     : () => dayOfTheYear(date),
      W     : () => weekOfTheYear(date),
      Q     : () => quarterOfTheYear(date),
      Z     : () => timezoneOffset(date),
      u     : () => totalDaysThisMonth(date),
      uu    : () => totalDaysThisYear(date),
      x     : () => daysLeftThisWeek(date),
      xx    : () => daysLeftThisMonth(date),
      xxx   : () => daysLeftThisYear(date),
      v     : () => isLeapYear(date),
      o     : () => ordinal(date.getDate()),
      O     : () => nameOfDay(date, 1),
      OO    : () => nameOfDay(date, 2),
      defaults : () => token.replace(/'/g, '')
    }
    return typeof tokens[token] !== 'function'
      ? tokens.defaults()
      : tokens[token]()
  }

  function antiPattern (mask, token) {
    if (!token.test(mask)) return mask
    return false
  }

  function nameOfDay (date, n) {
    var thisday = date.getDay()
    var name = dayNames.filter(dayOfWeek).toString()

    function dayOfWeek (val, idx, arr) {
      if (idx === thisday) return val
    }

    if (n == null) return name
    else return abbreviateDayName(name, n)

    function abbreviateDayName (name, num) {
      if (num > 3) {
        if (name === 'Tuesday') return name.slice(0, 4)
        else if (name === 'Thursday' && num < 6) return name.slice(0, num)
        else return name.slice(0, 3)
      }
      return name.slice(0, num)
    }
  }

  function nameOfMonth (date, n) {
    var thismonth = date.getMonth()
    var name = monthNames.filter(monthOfYear).toString()

    function monthOfYear (val, idx, arr) {
      if (idx === thismonth) return val
    }

    if (n == null) return name
    else return abbreviateName(name, n)

    function abbreviateName (name, num) {
      if (num > 3) num = 3
      return name.slice(0, num)
    }
  }

  function dayOfTheYear (date) {
    // var now = new Date(date.getTime())
    // var then = new Date(date.getFullYear(), 0, 1)

    var now = date.getTime()
    var then = new Date(date.getFullYear(), 0, 1)

    return Math.ceil((now - then) / 86400000)
  }

  function weekOfTheYear () {
    // Remove time components of date
    var targetThursday = new Date(date.getFullYear(), date.getMonth(), date.getDate())

    // Change date to Thursday same week
    targetThursday.setDate(targetThursday.getDate() - ((targetThursday.getDay() + 6) % 7) + 3)

    // Take January 4th as it is always in week 1 (see ISO 8601)
    var firstThursday = new Date(targetThursday.getFullYear(), 0, 4)

    // Change date to Thursday same week
    firstThursday.setDate(firstThursday.getDate() - ((firstThursday.getDay() + 6) % 7) + 3)

    // Check if daylight-saving-time-switch occured and correct for it
    var ds = targetThursday.getTimezoneOffset() - firstThursday.getTimezoneOffset()
    targetThursday.setHours(targetThursday.getHours() - ds)

    // Number of weeks between target Thursday and first Thursday
    var weekDiff = (targetThursday - firstThursday) / (86400000*7)
    return 1 + Math.floor(weekDiff)
  }

  function quarterOfTheYear (date) {
    var month = date.getMonth() + 1
    return (Math.ceil(month / 3))
  }

  function timezoneOffset (date) {
    var offset = date.getTimezoneOffset()
    var abs = Math.abs(offset)
    var hr = pad(Math.floor(abs / 60), 2)
    var min = pad(abs % 60, 2)
    return [(offset > 0 ? '-' : '+'), hr, `:${min}`].join('')
  }

  function totalDaysThisMonth (date) {
    var year = date.getFullYear()
    var month = date.getMonth() + 1

    return new Date(year, month, 0).getDate()
  }

  function totalDaysThisYear (date) {
    var thisYear = new Date(date).getFullYear()
    return isLeapYear(thisYear) ? 366 : 365
  }

  function isLeapYear (date) {
    var thisYear = isDate(date)
      ? new Date(date).getFullYear()
      : new Date().getFullYear()

    return thisYear % 400 === 0 || (thisYear % 100 !== 0 && thisYear % 4 === 0)
  }

  function daysLeftThisWeek (date) {
    return 6 - date.getDay()
  }

  function daysLeftThisMonth (date) {
    return totalDaysThisMonth(date) - date.getDate()
  }

  function daysLeftThisYear (date) {
    return totalDaysThisYear(date) - dayOfTheYear(date)
  }

  function ordinal (n) {
    n = toInt(n)
    return n + nth(n)
  }

  function nth (i) {
    return ['th', 'st', 'nd', 'rd'][i % 10 > 3 ? 0 : (i % 100 - i % 10 !== 10) * i % 10]
  }

  function toInt (val) {
    return ~~val
  }

  function isDate (value) {
    return Object.prototype.toString.call(value) === '[object Date]'
  }

  function pad (val, len) {
    val = String(val)
    len = len || 2
    while (val.length < len) {
      val = '0' + val
    }
    return val
  }
}

module.exports = clockin
<|endoftext|>


<commit_before>/*
 * Primary app code
 */

// Global app settings

APP_DATA = null;
APP_EVENTS= {};
APP_UTIL = {
    parseBoolOption: function(val, def) {
        if (val == null) {
            if (def === undefined)
                return true;
            else
                return def == true;
        }

        var str = (val + '').toLowerCase();
        return str == '' || str == 'true' || str == '1' || str == 'yes' || str == 'on';
    },

    isValidBoolOption: function(val) {
        if (val == null)
            return true;

        var t = (val + '').toLowerCase();
        return val == '' || t == '1' || t == 'true' || t == 'on' || t == 'yes' || t == '0' || t == 'false' || t == 'off' || t == 'no';
    },

    parseOptionStrings: function(val, dict, validateCallback) {
        dict = dict || {};
        var lines = val.replace('\r', '\n').split('\n');
        for(var i in lines) {
            var line = lines[i].trim();
            if (line != '' && line[0] != '#') {
                if (line.indexOf('--') == 0) {
                    line = line.substr(2);
                }

                var eqpos = line.indexOf('=');
                var key = line;
                var value = true;
                if (eqpos > 0) {
                    key = line.substr(0, eqpos).trim();
                    value = line.substr(eqpos + 1).trim();
                    if (value == '')
                        value = true;
                }

                if (validateCallback)
                    if (!validateCallback(dict, key, value))
                        return null;

                dict['--' + key] = value;
            }
        }

        return dict;
    },

    fill_form: function(form, data, map, extra) {
        map = map || {};
        data = data || {};

        for(var k in data) {
            var key = k;
            var m = map[key];
            var v = data[k];

            if (m !== false) {
                if (m && typeof(m) == typeof(''))
                    key = m;

                if (m && typeof(m) == typeof(function() {})) {
                    m(data, key, v, extra);
                } else {
                    var n = form.find('#' + key);
                    if (n.attr('type') == 'checkbox') {
                        n.attr('checked', APP_UTIL.parseBoolOption(v));
                    } else {
                        n.val(v);
                    }

                    n.change();
                }
            }
        }
    },

    read_form: function(form, map, values, extra) {
        values = values || {};
        map = map || {};

        form.find('select').each(function(i, e) {
            var key = e.id;
            var m = map[e.id];

            if (m !== false) {
                if (m && typeof(m) == typeof(function() {})) {
                    m(values, key, e, extra);
                } else {
                    if (m && typeof(m) == typeof(''))
                        key = m;

                    values[key] = $(e).val();
                }
            }
        });

        form.find('input').each(function(i, e) {
            var key = e.id;
            var m = map[e.id];

            if (m !== false) {
                if (m && typeof(m) == typeof(function() {})) {
                    m(values, key, e, extra);
                } else {
                    if (m && typeof(m) == typeof(''))
                        key = m;

                if (e.type == 'checkbox')
                    values[key] = $(e).is(':checked');
                else
                    values[key] = $(e).val();
                }
            }
        });

        return values;
    }
};

$(document).ready(function() {

    // Flag as loaded in case we have plugins
    APP_DATA = {
        plugins: {
            backend: {},
            compression: {},
            encryption: {}
        }
    };

    var PRIVATE_DATA = {};

    $('#main-appname').text(APP_CONFIG.branded_name);

    if ((APP_CONFIG.branded_subtitle || '').length > 0) {
        var subdiv = $('
');
        subdiv.text(APP_CONFIG.branded_subtitle);
        $('#main-appname').append(subdiv);
        $('#main-appname').addClass('has-subtitle');
    }

    $('.button').button();

    $('#main-list-container > div.main-backup-entry').remove();
    $('#loading-dialog').dialog({modal: true}).show();

    // Register a global function for password strength
    $.passwordStrength = function(password, callback) {
        if (callback == null)
            return;

        var onUpdate = function(res) {
            try { callback(res); }
            catch (e) { }
        };

        try { onUpdate(zxcvbn(password)); }
        catch (e) {
            // Not loaded, try this:
            $.getScript('/scripts/zxcvbn.js', function() {
                try {
                    onUpdate(zxcvbn(password));
                }
                catch (e) {
                    onUpdate(null);
                }
            });
        }
    };

    PRIVATE_DATA.refresh_server_settings = function(callback, errorhandler) {
        $.ajax({
            url: APP_CONFIG.server_url,
            dataType: 'json',
            data: { action: 'system-info' }
        })
        .done(function(data) {
            PRIVATE_DATA.server_config = data;
            $('#loading-dialog').dialog("close");
            if (callback != null)
                callback(PRIVATE_DATA.server_config);
        })
        .fail(function(data, status) {
            if (errorhandler)
                errorhandler(data, status);
        });
    };

    PRIVATE_DATA.refresh_backup_list = function(callback, errorhandler) {
        $.ajax({
            url: APP_CONFIG.server_url,
            dataType: 'json',
            data: { action: 'list-backups' }
        })
        .done(function(data) {
            PRIVATE_DATA.backup_list = data;
            $('#loading-dialog').dialog("close");

            // Clear existing stuff
            $('#main-list-container > div.main-backup-entry').remove();
            if ($('#backup-item-template').length > 0 && data.length > 0)
                $.tmpl($('#backup-item-template'), data).prependTo($('#main-list-container'));

            //Fill with jQuery template

            if (callback != null)
                callback(PRIVATE_DATA.backup_list);
        })
        .fail(function(data, status) {
            if (errorhandler)
                errorhandler(data, status);
        });
    };

    PRIVATE_DATA.refresh_server_settings();
    PRIVATE_DATA.refresh_backup_list();

    APP_DATA.getServerConfig = function(callback, errorhandler) {
        if (PRIVATE_DATA.server_config == null) {
            PRIVATE_DATA.refresh_server_settings(callback, errorhandler);
        } else {
            callback(PRIVATE_DATA.server_config);
        }
    };

    APP_DATA.validatePath = function(path, callback) {
        $.ajax({
            url: APP_CONFIG.server_url,
            dataType: 'json',
            data: { action: 'validate-path', path: path }
        })
        .done(function(data) {
            if (callback != null)
                callback(path, true, null);
        })
        .fail(function(data, status) {
            if (callback)
                callback(path, false, status);
        });
    };

    APP_DATA.getLabels = function(callback) {
        $.ajax({
            url: APP_CONFIG.server_url,
            dataType: 'json',
            data: { action: 'list-tags' }
        })
        .done(function(data) {
            if (callback != null)
                callback(data, true, null);
        })
        .fail(function(data, status) {
            if (callback)
                callback(null, false, status);
        });
    };

    APP_DATA.getBackupDefaults = function(callback, errorhandler) {
        $.ajax({
            url: APP_CONFIG.server_url,
            dataType: 'json',
            data: { action: 'get-backup-defaults' }
        })
        .done(function(data) {
            if (callback != null)
                callback(data, true, null);
        })
        .fail(function(data, status) {
            if (callback)
                callback(null, false, status);
        });
    };

    $('#main-settings').click(function() {
        var pos = $('#main-settings').position();
        var barheight = $('#main-topbar').outerHeight();
        var menuwidth = $('#main-control-menu').outerWidth();
        var buttonwidth = $('#main-settings').outerWidth();

        $('#main-control-menu').css({
            position: 'absolute',
            top: barheight + 'px',
            right: ($(document).outerWidth() - (pos.left + buttonwidth)) + 'px'
        });
        $('#main-control-menu').toggle();
    });

    $('#main-donate').click(function() {

    });

    $('#main-control').click(function() {
    });

    $('#main-newbackup').click(function() {
        APP_DATA.getServerConfig(function(data) {
            APP_DATA.getBackupDefaults(function(defaults) {
                $("#edit-dialog").dialog('open');

                // Bug-fix, this will remove style="width: auto", which breaks Chrome a bit
                $("#edit-dialog").css('width', '');

                // Send the defaults to the dialog
                $("#edit-dialog").trigger('setup-dialog', defaults.data);
            },
            function() {
                alert('Failed to get server setup...')
            })
        },
        function() {
            alert('Failed to get server setup...')
        });

    });

    $('#edit-dialog').tabs({ active: 0 });
    $("#edit-dialog").dialog({
        minWidth: 320,
        width: $('body').width > 600 ? 320 : 600,
        minHeight: 480,
        height: 500,
        modal: true,
        autoOpen: false,
        closeOnEscape: true,
        buttons: [
            { text: 'Previous', disabled: true, click: function(event, ui) {
                var cur = parseInt($('#edit-dialog').tabs( "option", "active"));
                cur = Math.max(cur-1, 0);
                $('#edit-dialog').tabs( "option", "active", cur);
            }},
            { text: 'Next', click: function(event, ui) {
                var cur = parseInt($('#edit-dialog').tabs( "option", "active"));
                var max = $('#edit-dialog').parent().find('[role=tablist] > li').size() - 1;

                if (!EDIT_BACKUP.validate_tab(cur))
                    return;

                event.curPage = cur;
                event.currentTarget.curPage = cur;
                cur = Math.min(cur+1, max);
                $('#edit-dialog').tabs( "option", "active", cur);
            }}
        ]
    });

    // Hack the tabs into the dialog title
    $('#edit-dialog').parent().find('.ui-dialog-titlebar').after($('#edit-dialog').find('.ui-tabs-nav'));
    $('#edit-dialog').parent().addClass('ui-tabs');

});<commit_msg>Better error message on server faults<commit_after>/*
 * Primary app code
 */

// Global app settings

APP_DATA = null;
APP_EVENTS= {};
APP_UTIL = {
    parseBoolOption: function(val, def) {
        if (val == null) {
            if (def === undefined)
                return true;
            else
                return def == true;
        }

        var str = (val + '').toLowerCase();
        return str == '' || str == 'true' || str == '1' || str == 'yes' || str == 'on';
    },

    isValidBoolOption: function(val) {
        if (val == null)
            return true;

        var t = (val + '').toLowerCase();
        return val == '' || t == '1' || t == 'true' || t == 'on' || t == 'yes' || t == '0' || t == 'false' || t == 'off' || t == 'no';
    },

    parseOptionStrings: function(val, dict, validateCallback) {
        dict = dict || {};
        var lines = val.replace('\r', '\n').split('\n');
        for(var i in lines) {
            var line = lines[i].trim();
            if (line != '' && line[0] != '#') {
                if (line.indexOf('--') == 0) {
                    line = line.substr(2);
                }

                var eqpos = line.indexOf('=');
                var key = line;
                var value = true;
                if (eqpos > 0) {
                    key = line.substr(0, eqpos).trim();
                    value = line.substr(eqpos + 1).trim();
                    if (value == '')
                        value = true;
                }

                if (validateCallback)
                    if (!validateCallback(dict, key, value))
                        return null;

                dict['--' + key] = value;
            }
        }

        return dict;
    },

    fill_form: function(form, data, map, extra) {
        map = map || {};
        data = data || {};

        for(var k in data) {
            var key = k;
            var m = map[key];
            var v = data[k];

            if (m !== false) {
                if (m && typeof(m) == typeof(''))
                    key = m;

                if (m && typeof(m) == typeof(function() {})) {
                    m(data, key, v, extra);
                } else {
                    var n = form.find('#' + key);
                    if (n.attr('type') == 'checkbox') {
                        n.attr('checked', APP_UTIL.parseBoolOption(v));
                    } else {
                        n.val(v);
                    }

                    n.change();
                }
            }
        }
    },

    read_form: function(form, map, values, extra) {
        values = values || {};
        map = map || {};

        form.find('select').each(function(i, e) {
            var key = e.id;
            var m = map[e.id];

            if (m !== false) {
                if (m && typeof(m) == typeof(function() {})) {
                    m(values, key, e, extra);
                } else {
                    if (m && typeof(m) == typeof(''))
                        key = m;

                    values[key] = $(e).val();
                }
            }
        });

        form.find('input').each(function(i, e) {
            var key = e.id;
            var m = map[e.id];

            if (m !== false) {
                if (m && typeof(m) == typeof(function() {})) {
                    m(values, key, e, extra);
                } else {
                    if (m && typeof(m) == typeof(''))
                        key = m;

                if (e.type == 'checkbox')
                    values[key] = $(e).is(':checked');
                else
                    values[key] = $(e).val();
                }
            }
        });

        return values;
    }
};

$(document).ready(function() {

    // Flag as loaded in case we have plugins
    APP_DATA = {
        plugins: {
            backend: {},
            compression: {},
            encryption: {}
        }
    };

    var PRIVATE_DATA = {};

    $('#main-appname').text(APP_CONFIG.branded_name);

    if ((APP_CONFIG.branded_subtitle || '').length > 0) {
        var subdiv = $('
');
        subdiv.text(APP_CONFIG.branded_subtitle);
        $('#main-appname').append(subdiv);
        $('#main-appname').addClass('has-subtitle');
    }

    $('.button').button();

    $('#main-list-container > div.main-backup-entry').remove();
    $('#loading-dialog').dialog({modal: true}).show();

    // Register a global function for password strength
    $.passwordStrength = function(password, callback) {
        if (callback == null)
            return;

        var onUpdate = function(res) {
            try { callback(res); }
            catch (e) { }
        };

        try { onUpdate(zxcvbn(password)); }
        catch (e) {
            // Not loaded, try this:
            $.getScript('/scripts/zxcvbn.js', function() {
                try {
                    onUpdate(zxcvbn(password));
                }
                catch (e) {
                    onUpdate(null);
                }
            });
        }
    };

    PRIVATE_DATA.refresh_server_settings = function(callback, errorhandler) {
        $.ajax({
            url: APP_CONFIG.server_url,
            dataType: 'json',
            data: { action: 'system-info' }
        })
        .done(function(data) {
            PRIVATE_DATA.server_config = data;
            $('#loading-dialog').dialog("close");
            if (callback != null)
                callback(PRIVATE_DATA.server_config);
        })
        .fail(function(data, status) {
            if (errorhandler)
                errorhandler(data, status);
        });
    };

    PRIVATE_DATA.refresh_backup_list = function(callback, errorhandler) {
        $.ajax({
            url: APP_CONFIG.server_url,
            dataType: 'json',
            data: { action: 'list-backups' }
        })
        .done(function(data) {
            PRIVATE_DATA.backup_list = data;
            $('#loading-dialog').dialog("close");

            // Clear existing stuff
            $('#main-list-container > div.main-backup-entry').remove();
            if ($('#backup-item-template').length > 0 && data.length > 0)
                $.tmpl($('#backup-item-template'), data).prependTo($('#main-list-container'));

            //Fill with jQuery template

            if (callback != null)
                callback(PRIVATE_DATA.backup_list);
        })
        .fail(function(data, status) {
            if (errorhandler)
                errorhandler(data, status);
        });
    };

    PRIVATE_DATA.refresh_server_settings();
    PRIVATE_DATA.refresh_backup_list();

    APP_DATA.getServerConfig = function(callback, errorhandler) {
        if (PRIVATE_DATA.server_config == null) {
            PRIVATE_DATA.refresh_server_settings(callback, errorhandler);
        } else {
            callback(PRIVATE_DATA.server_config);
        }
    };

    APP_DATA.validatePath = function(path, callback) {
        $.ajax({
            url: APP_CONFIG.server_url,
            dataType: 'json',
            data: { action: 'validate-path', path: path }
        })
        .done(function(data) {
            if (callback != null)
                callback(path, true, null);
        })
        .fail(function(data, status) {
            if (callback)
                callback(path, false, data.statusText);
        });
    };

    APP_DATA.getLabels = function(callback) {
        $.ajax({
            url: APP_CONFIG.server_url,
            dataType: 'json',
            data: { action: 'list-tags' }
        })
        .done(function(data) {
            if (callback != null)
                callback(data, true, null);
        })
        .fail(function(data, status) {
            if (callback)
                callback(null, false, data.statusText);
        });
    };

    APP_DATA.getBackupDefaults = function(callback, errorhandler) {
        $.ajax({
            url: APP_CONFIG.server_url,
            dataType: 'json',
            data: { action: 'get-backup-defaults' }
        })
        .done(function(data) {
            if (callback != null)
                callback(data, true, null);
        })
        .fail(function(data, status) {
            if (errorhandler)
                errorhandler(null, false, data.statusText);
        });
    };

    $('#main-settings').click(function() {
        var pos = $('#main-settings').position();
        var barheight = $('#main-topbar').outerHeight();
        var menuwidth = $('#main-control-menu').outerWidth();
        var buttonwidth = $('#main-settings').outerWidth();

        $('#main-control-menu').css({
            position: 'absolute',
            top: barheight + 'px',
            right: ($(document).outerWidth() - (pos.left + buttonwidth)) + 'px'
        });
        $('#main-control-menu').toggle();
    });

    $('#main-donate').click(function() {

    });

    $('#main-control').click(function() {
    });

    $('#main-newbackup').click(function() {
        APP_DATA.getServerConfig(function(data) {
            APP_DATA.getBackupDefaults(function(defaults) {
                $("#edit-dialog").dialog('open');

                // Bug-fix, this will remove style="width: auto", which breaks Chrome a bit
                $("#edit-dialog").css('width', '');

                // Send the defaults to the dialog
                $("#edit-dialog").trigger('setup-dialog', defaults.data);
            },
            function() {
                alert('Failed to get server setup...')
            })
        },
        function() {
            alert('Failed to get server setup...')
        });

    });

    $('#edit-dialog').tabs({ active: 0 });
    $("#edit-dialog").dialog({
        minWidth: 320,
        width: $('body').width > 600 ? 320 : 600,
        minHeight: 480,
        height: 500,
        modal: true,
        autoOpen: false,
        closeOnEscape: true,
        buttons: [
            { text: 'Previous', disabled: true, click: function(event, ui) {
                var cur = parseInt($('#edit-dialog').tabs( "option", "active"));
                cur = Math.max(cur-1, 0);
                $('#edit-dialog').tabs( "option", "active", cur);
            }},
            { text: 'Next', click: function(event, ui) {
                var cur = parseInt($('#edit-dialog').tabs( "option", "active"));
                var max = $('#edit-dialog').parent().find('[role=tablist] > li').size() - 1;

                if (!EDIT_BACKUP.validate_tab(cur))
                    return;

                event.curPage = cur;
                event.currentTarget.curPage = cur;
                cur = Math.min(cur+1, max);
                $('#edit-dialog').tabs( "option", "active", cur);
            }}
        ]
    });

    // Hack the tabs into the dialog title
    $('#edit-dialog').parent().find('.ui-dialog-titlebar').after($('#edit-dialog').find('.ui-tabs-nav'));
    $('#edit-dialog').parent().addClass('ui-tabs');

});<|endoftext|>


<commit_before>/*
Copyright 2015 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');

module.exports = {
    defaultAvatarUrlForString: function(s) {
        var total = 0;
        for (var i = 0; i < s.length; ++i) {
            total += s.charCodeAt(i);
        }
        switch (total % 3) {
            case 0:
                return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9QjNbxSKP4eagAFnTseHFErFYLBaLxWKxWCwWi8Vi8cX4CzAABSwCRWJw31gAAAAASUVORK5CYII=";
            case 1:
                return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9chOaxgCP4eagAFk9seHFErFYLBaLxWKxWCwWi8Vi8cX4CzAAtKMCks/JG8MAAAAASUVORK5CYII=";
            case 2:
                return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9YzNayQCP4eagADldseHFErFYLBaLxWKxWCwWi8Vi8cX4CzAAyiACeHwPiu4AAAAASUVORK5CYII=";
        }
    }
}

<commit_msg>Fix notifications<commit_after>/*
Copyright 2015 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');

module.exports = {
    avatarUrlForMember: function(member, width, height, resizeMethod) {
        var url = MatrixClientPeg.get().getAvatarUrlForMember(
            member,
            width,
            height,
            resizeMethod
        );
        if (!url) {
            url = this.defaultAvatarUrlForString(member.userId);
        }
        return url;
    },

    defaultAvatarUrlForString: function(s) {
        var total = 0;
        for (var i = 0; i < s.length; ++i) {
            total += s.charCodeAt(i);
        }
        switch (total % 3) {
            case 0:
                return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9QjNbxSKP4eagAFnTseHFErFYLBaLxWKxWCwWi8Vi8cX4CzAABSwCRWJw31gAAAAASUVORK5CYII=";
            case 1:
                return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9chOaxgCP4eagAFk9seHFErFYLBaLxWKxWCwWi8Vi8cX4CzAAtKMCks/JG8MAAAAASUVORK5CYII=";
            case 2:
                return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9YzNayQCP4eagADldseHFErFYLBaLxWKxWCwWi8Vi8cX4CzAAyiACeHwPiu4AAAAASUVORK5CYII=";
        }
    }
}

<|endoftext|>


<commit_before>var express = require('express')
  , logger = require('morgan')
  , app = express()
  , mongodb = require('mongodb')
  , homepage = require('pug').compileFile(__dirname + '/source/templates/homepage.pug')
  , ra = require('pug').compileFile(__dirname + '/source/templates/ra.pug')
  , MongoClient = mongodb.MongoClient
  , mongodb_url = 'mongodb://localhost:27017/rally'

app.use(logger('dev'))
app.use(express.static(__dirname + '/static'))

app.get('/', function (req, res, next) {
  try {
    MongoClient.connect(url, function(err, db) {
      if (err) {
        console.log('Unable to connect to database server', err)
      } else {
        console.log('Connection to database established')
        var collection = db.collection('rallyamerica')
        collection.find({}, function(err, data) {
          if (err) {
            res.send(err)
          } else if (data) {
            res.send(homepage({
              data: data
            }))
          }
        })
      }
    })
  } catch(e) {
    next(e)
  }
})

app.get('/ra/:year/:event_code', function(req, res, next) {
  try {
    var year = req.params.year
    var event_code = req.params.event_code

    console.log(year)
    console.log(event_code)

    MongoClient.connect(mongodb_url, function(err, db) {
      if (err) {
        console.log('Unable to connect to database server', err)
      } else {
        console.log('Connection to database established')
        var collection = db.collection('ra_events')
        collection.findOne({
          'year':year,
          'event_code':event_code
        }, function(err, data) {
          if (err) {
            res.send(err)
          } else if (data) {
            res.send(ra({
              data: data
            }))
          }
        })
      }
    })
  } catch(e) {
    next(e)
  }
})

app.listen(process.env.PORT || 80, function() {
  console.log('Listening on http://localhost:' + (process.env.PORT || 80))
})
<commit_msg>homepage test 3<commit_after>var express = require('express')
  , logger = require('morgan')
  , app = express()
  , mongodb = require('mongodb')
  , homepage = require('pug').compileFile(__dirname + '/source/templates/homepage.pug')
  , ra = require('pug').compileFile(__dirname + '/source/templates/ra.pug')
  , MongoClient = mongodb.MongoClient
  , mongodb_url = 'mongodb://localhost:27017/rally'

app.use(logger('dev'))
app.use(express.static(__dirname + '/static'))

app.get('/', function (req, res, next) {
  try {
    MongoClient.connect(mongodb_url, function(err, db) {
      if (err) {
        console.log('Unable to connect to database server', err)
      } else {
        console.log('Connection to database established')
        var collection = db.collection('rallyamerica')
        collection.find({}, function(err, data) {
          if (err) {
            res.send(err)
          } else if (data) {
            res.send(homepage({
              data: data
            }))
          }
        })
      }
    })
  } catch(e) {
    next(e)
  }
})

app.get('/ra/:year/:event_code', function(req, res, next) {
  try {
    var year = req.params.year
    var event_code = req.params.event_code

    console.log(year)
    console.log(event_code)

    MongoClient.connect(mongodb_url, function(err, db) {
      if (err) {
        console.log('Unable to connect to database server', err)
      } else {
        console.log('Connection to database established')
        var collection = db.collection('ra_events')
        collection.findOne({
          'year':year,
          'event_code':event_code
        }, function(err, data) {
          if (err) {
            res.send(err)
          } else if (data) {
            res.send(ra({
              data: data
            }))
          }
        })
      }
    })
  } catch(e) {
    next(e)
  }
})

app.listen(process.env.PORT || 80, function() {
  console.log('Listening on http://localhost:' + (process.env.PORT || 80))
})
<|endoftext|>


<commit_before><commit_msg>style: lint<commit_after><|endoftext|>


<commit_before>const express = require('express')
const cookieParser = require('cookie-parser')
require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` })

const app = express()
app.use(express.json())
app.use(cookieParser())

app.set('view engine', 'pug')
const PORT = 3000
const demoHomeUrl = process.env.DEMO_HOME_URL
const publisherUrl = process.env.PUBLISHER_URL
const advertiserUrl = process.env.ADVERTISER_URL
const adtechUrl = process.env.ADTECH_URL

app.use(express.static('static'))

app.get('/', (req, res) => {
  res.render('index')
})

/* -------------------------------------------------------------------------- */
/*                               Debugging setup                              */
/* -------------------------------------------------------------------------- */

app.use(function (req, res, next) {
  // Optional: set a regular measurement 3P cookie
  const legacyMeasurementCookie = req.cookies['measure']
  if (legacyMeasurementCookie === undefined) {
    const cookieValue = Math.floor(Math.random() * 1000000000000000)
    res.set(
      'Set-Cookie',
      `measure=${cookieValue}; SameSite=None; Secure; HttpOnly`
    )
  }

  // Set the Attribution Reporting debug cookie, if not already set
  const debugCookie = req.cookies['ar_debug']
  if (debugCookie === undefined) {
    console.log('No debug cookie set. Setting it now.')
    res.set('Set-Cookie', 'ar_debug=1; SameSite=None; Secure; HttpOnly')
  }
  next()
})

/* -------------------------------------------------------------------------- */
/*                                 Ad serving                                 */
/* -------------------------------------------------------------------------- */

app.get('/register-source', (req, res) => {
  // Send a response with the header Attribution-Reporting-Register-Source in order to ask the browser to register a source event
  const attributionDestination = process.env.ADVERTISER_URL
  // For demo purposes, sourceEventId is a random ID. In a real system, this ID would be tied to a unique serving-time identifier mapped to any information an adtech provider may need
  const sourceEventId = Math.floor(Math.random() * 1000000000000000)
  const legacyMeasurementCookie = req.cookies['measure']
  res.set(
    'Attribution-Reporting-Register-Source',
    JSON.stringify({
      source_event_id: `${sourceEventId}`,
      destination: attributionDestination,
      expiry: '604800',
      // Optional: set a debug key, and give it the value of the legacy measurement 3P cookie.
      // This is a simplist approach fr demo purposes. In a real system, you would still make this key a unique ID, but you may map it key to additional source-time information you think will be relevant for you to debug.
      debug_key: `${legacyMeasurementCookie}`
    })
  )
  res.status(200).send('OK')
})

app.get('/ad-click', (req, res) => {
  const href = `${process.env.ADVERTISER_URL}`
  res.render('ad-click', {
    href,
    attributionsrc: `${adtechUrl}/register-source`
  })
})

app.get('/ad-click-js', (req, res) => {
  const href = `${process.env.ADVERTISER_URL}`
  res.render('ad-click-js', {
    href,
    attributionsrc: `${adtechUrl}/register-source`
  })
})

app.get('/ad-view-img', (req, res) => {
  const href = `${process.env.ADVERTISER_URL}`
  res.render('ad-view-img', {
    attributionsrc: `${adtechUrl}/register-source`
  })
})

app.get('/ad-view-js', (req, res) => {
  const href = `${process.env.ADVERTISER_URL}`
  res.render('ad-view-js', {
    attributionsrc: `${adtechUrl}/register-source`
  })
})

app.get('/ad-script-view-img', (req, res) => {
  res.set('Content-Type', 'text/javascript')
  const adUrl = `${process.env.ADTECH_URL}/ad-view-img`
  const iframe = `<iframe src='${adUrl}' allow='attribution-reporting' width=190 height=190 scrolling=no frameborder=1 padding=0></iframe>`
  res.send(`document.write("${iframe}");`)
})

app.get('/ad-script-view-js', (req, res) => {
  res.set('Content-Type', 'text/javascript')
  const adUrl = `${process.env.ADTECH_URL}/ad-view-js`
  const iframe = `<iframe src='${adUrl}' allow='attribution-reporting' width=190 height=190 scrolling=no frameborder=1 padding=0></iframe>`
  res.send(`document.write("${iframe}");`)
})

app.get('/ad-script-click-element', (req, res) => {
  res.set('Content-Type', 'text/javascript')
  const adClickUrl = `${process.env.ADTECH_URL}/ad-click`
  const iframe = `<iframe src='${adClickUrl}' allow='attribution-reporting' width=190 height=190 scrolling=no frameborder=1 padding=0></iframe>`
  res.send(`document.write("${iframe}");`)
})

app.get('/ad-script-click-js', (req, res) => {
  res.set('Content-Type', 'text/javascript')
  const adClickNoLinkUrl = `${process.env.ADTECH_URL}/ad-click-js`
  const iframe = `<iframe src='${adClickNoLinkUrl}' allow='attribution-reporting' width=190 height=190 scrolling=no frameborder=1 padding=0></iframe>`
  res.send(`document.write("${iframe}");`)
})

/* -------------------------------------------------------------------------- */
/*                     Attribution trigger (conversion)                       */
/* -------------------------------------------------------------------------- */

const CHECKOUT_COMPLETED = 'checkout-completed'
const ADD_TO_CART = 'add-to-cart'
const VISIT_PRODUCT_PAGE = 'visit-product-page'
const SIGNUP_NEWSLETTER = 'signup-newsletter'

const conversionValues = {
  // checkout = 1, so that the value is consistent across clicks and views
  // trigger data for views (event sources) must be 0 or 1 (1 bit)
  // trigger data for clicks (event sources) must be a value between 0 and 7 (3 bits)
  [CHECKOUT_COMPLETED]: 1,
  [ADD_TO_CART]: 2,
  [VISIT_PRODUCT_PAGE]: 3,
  [SIGNUP_NEWSLETTER]: 4
}

function getTriggerData(conversionType) {
  return conversionValues[conversionType]
}

function getPriority(conversionType, usePriorities) {
  if (!usePriorities) {
    // false: No conversion should be prioritized specifically => always return a prio of 0
    return 0
  } else {
    // Assign a priority of 100 to checkouts, and of 0 to other conversion types
    return conversionType === CHECKOUT_COMPLETED ? 100 : 0
  }
}

app.get('/conversion', (req, res) => {
  const conversionType = req.query['conversion-type']
  const triggerData = getTriggerData(conversionType)

  const usePriorities = req.query['prio-checkout'] === 'true'
  const priority = getPriority(conversionType, usePriorities)

  const deduplicationKey = req.query['purchase-id']
  // Use deduplication only if it's on in the app settings and if a deduplication key is presents
  const useDeduplication = !!(deduplicationKey && req.query['dedup'] === 'true')

  const legacyMeasurementCookie = req.cookies['measure']
  // Optional: set a debug key, and give it the value of the legacy measurement 3P cookie.
  // This is a simplist approach fr demo purposes. In a real system, you would still make this key a unique ID, but you may map it key to additional trigger-time information you think will be relevant for you to debug.
  res.set(
    'Attribution-Reporting-Trigger-Debug-Key',
    `${legacyMeasurementCookie}`
  )

  // Instruct the browser to schedule-send a report
  res.set(
    'Attribution-Reporting-Register-Event-Trigger',
    // ⚠ Note the enclosing []!
    JSON.stringify([
      {
        trigger_data: `${triggerData}`,
        debug_key: '123',
        // if priorities are on, specify the priority
        ...(usePriorities && { priority: `${priority}` }),
        // if deduplication is on, specify the deduplication key
        ...(useDeduplication && { deduplication_key: deduplicationKey })
      }
    ])
  )
  res.sendStatus(200)
})

/* -------------------------------------------------------------------------- */
/*                                 Reports                                    */
/* -------------------------------------------------------------------------- */

let reports = []

app.get('/reports', (req, res) => {
  res.send(JSON.stringify(reports))
})

app.post(
  '/.well-known/attribution-reporting/debug/report-event-attribution',
  async (req, res) => {
    console.log('DEBUG REPORT RECEIVED:', req.body)
    const newReport = { ...req.body, date: new Date() }
    reports = [newReport, ...reports]
    console.log(
      '\x1b[1;31m%s\x1b[0m',
      `🚀 Adtech has received a debug report from the browser`
    )
    res.sendStatus(200)
  }
)

app.post(
  '/.well-known/attribution-reporting/report-event-attribution',
  async (req, res) => {
    console.log('REGULAR REPORT RECEIVED:', req.body)
    const newReport = { ...req.body, date: new Date() }
    reports = [newReport, ...reports]
    console.log(
      '\x1b[1;31m%s\x1b[0m',
      `🚀 Adtech has received a report from the browser`
    )
    res.sendStatus(200)
  }
)

const listener = app.listen(process.env.PORT || PORT, () => {
  console.log(
    '\x1b[1;31m%s\x1b[0m',
    `🚀 Adtech server is listening on port ${listener.address().port}`
  )
})
<commit_msg>Docs: commented cookie code<commit_after>const express = require('express')
const cookieParser = require('cookie-parser')
require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` })

const app = express()
app.use(express.json())
app.use(cookieParser())

app.set('view engine', 'pug')
const PORT = 3000
const demoHomeUrl = process.env.DEMO_HOME_URL
const publisherUrl = process.env.PUBLISHER_URL
const advertiserUrl = process.env.ADVERTISER_URL
const adtechUrl = process.env.ADTECH_URL

app.use(express.static('static'))

app.get('/', (req, res) => {
  res.render('index')
})

/* -------------------------------------------------------------------------- */
/*                               Debugging setup                              */
/* -------------------------------------------------------------------------- */

app.use(function (req, res, next) {
  // Optional: set a regular measurement 3P cookie
  const legacyMeasurementCookie = req.cookies['measure']
  if (legacyMeasurementCookie === undefined) {
    const cookieValue = Math.floor(Math.random() * 1000000000000000)
    res.set(
      'Set-Cookie',
      `measure=${cookieValue}; SameSite=None; Secure; HttpOnly`
    )
  }

  // Set the Attribution Reporting debug cookie
  const debugCookie = req.cookies['ar_debug']
  if (debugCookie === undefined) {
    res.set('Set-Cookie', 'ar_debug=1; SameSite=None; Secure; HttpOnly')
  }
  next()
})

/* -------------------------------------------------------------------------- */
/*                                 Ad serving                                 */
/* -------------------------------------------------------------------------- */

app.get('/register-source', (req, res) => {
  // Send a response with the header Attribution-Reporting-Register-Source in order to ask the browser to register a source event
  const attributionDestination = process.env.ADVERTISER_URL
  // For demo purposes, sourceEventId is a random ID. In a real system, this ID would be tied to a unique serving-time identifier mapped to any information an adtech provider may need
  const sourceEventId = Math.floor(Math.random() * 1000000000000000)
  const legacyMeasurementCookie = req.cookies['measure']
  res.set(
    'Attribution-Reporting-Register-Source',
    JSON.stringify({
      source_event_id: `${sourceEventId}`,
      destination: attributionDestination,
      expiry: '604800',
      // Optional: set a debug key, and give it the value of the legacy measurement 3P cookie.
      // This is a simplist approach fr demo purposes. In a real system, you would still make this key a unique ID, but you may map it key to additional source-time information you think will be relevant for you to debug.
      debug_key: `${legacyMeasurementCookie}`
    })
  )
  res.status(200).send('OK')
})

app.get('/ad-click', (req, res) => {
  const href = `${process.env.ADVERTISER_URL}`
  res.render('ad-click', {
    href,
    attributionsrc: `${adtechUrl}/register-source`
  })
})

app.get('/ad-click-js', (req, res) => {
  const href = `${process.env.ADVERTISER_URL}`
  res.render('ad-click-js', {
    href,
    attributionsrc: `${adtechUrl}/register-source`
  })
})

app.get('/ad-view-img', (req, res) => {
  const href = `${process.env.ADVERTISER_URL}`
  res.render('ad-view-img', {
    attributionsrc: `${adtechUrl}/register-source`
  })
})

app.get('/ad-view-js', (req, res) => {
  const href = `${process.env.ADVERTISER_URL}`
  res.render('ad-view-js', {
    attributionsrc: `${adtechUrl}/register-source`
  })
})

app.get('/ad-script-view-img', (req, res) => {
  res.set('Content-Type', 'text/javascript')
  const adUrl = `${process.env.ADTECH_URL}/ad-view-img`
  const iframe = `<iframe src='${adUrl}' allow='attribution-reporting' width=190 height=190 scrolling=no frameborder=1 padding=0></iframe>`
  res.send(`document.write("${iframe}");`)
})

app.get('/ad-script-view-js', (req, res) => {
  res.set('Content-Type', 'text/javascript')
  const adUrl = `${process.env.ADTECH_URL}/ad-view-js`
  const iframe = `<iframe src='${adUrl}' allow='attribution-reporting' width=190 height=190 scrolling=no frameborder=1 padding=0></iframe>`
  res.send(`document.write("${iframe}");`)
})

app.get('/ad-script-click-element', (req, res) => {
  res.set('Content-Type', 'text/javascript')
  const adClickUrl = `${process.env.ADTECH_URL}/ad-click`
  const iframe = `<iframe src='${adClickUrl}' allow='attribution-reporting' width=190 height=190 scrolling=no frameborder=1 padding=0></iframe>`
  res.send(`document.write("${iframe}");`)
})

app.get('/ad-script-click-js', (req, res) => {
  res.set('Content-Type', 'text/javascript')
  const adClickNoLinkUrl = `${process.env.ADTECH_URL}/ad-click-js`
  const iframe = `<iframe src='${adClickNoLinkUrl}' allow='attribution-reporting' width=190 height=190 scrolling=no frameborder=1 padding=0></iframe>`
  res.send(`document.write("${iframe}");`)
})

/* -------------------------------------------------------------------------- */
/*                     Attribution trigger (conversion)                       */
/* -------------------------------------------------------------------------- */

const CHECKOUT_COMPLETED = 'checkout-completed'
const ADD_TO_CART = 'add-to-cart'
const VISIT_PRODUCT_PAGE = 'visit-product-page'
const SIGNUP_NEWSLETTER = 'signup-newsletter'

const conversionValues = {
  // checkout = 1, so that the value is consistent across clicks and views
  // trigger data for views (event sources) must be 0 or 1 (1 bit)
  // trigger data for clicks (event sources) must be a value between 0 and 7 (3 bits)
  [CHECKOUT_COMPLETED]: 1,
  [ADD_TO_CART]: 2,
  [VISIT_PRODUCT_PAGE]: 3,
  [SIGNUP_NEWSLETTER]: 4
}

function getTriggerData(conversionType) {
  return conversionValues[conversionType]
}

function getPriority(conversionType, usePriorities) {
  if (!usePriorities) {
    // false: No conversion should be prioritized specifically => always return a prio of 0
    return 0
  } else {
    // Assign a priority of 100 to checkouts, and of 0 to other conversion types
    return conversionType === CHECKOUT_COMPLETED ? 100 : 0
  }
}

app.get('/conversion', (req, res) => {
  const conversionType = req.query['conversion-type']
  const triggerData = getTriggerData(conversionType)

  const usePriorities = req.query['prio-checkout'] === 'true'
  const priority = getPriority(conversionType, usePriorities)

  const deduplicationKey = req.query['purchase-id']
  // Use deduplication only if it's on in the app settings and if a deduplication key is presents
  const useDeduplication = !!(deduplicationKey && req.query['dedup'] === 'true')

  const legacyMeasurementCookie = req.cookies['measure']
  // Optional: set a debug key, and give it the value of the legacy measurement 3P cookie.
  // This is a simplist approach fr demo purposes. In a real system, you would still make this key a unique ID, but you may map it key to additional trigger-time information you think will be relevant for you to debug.
  res.set(
    'Attribution-Reporting-Trigger-Debug-Key',
    `${legacyMeasurementCookie}`
  )

  // Instruct the browser to schedule-send a report
  res.set(
    'Attribution-Reporting-Register-Event-Trigger',
    // ⚠ Note the enclosing []!
    JSON.stringify([
      {
        trigger_data: `${triggerData}`,
        debug_key: '123',
        // if priorities are on, specify the priority
        ...(usePriorities && { priority: `${priority}` }),
        // if deduplication is on, specify the deduplication key
        ...(useDeduplication && { deduplication_key: deduplicationKey })
      }
    ])
  )
  res.sendStatus(200)
})

/* -------------------------------------------------------------------------- */
/*                                 Reports                                    */
/* -------------------------------------------------------------------------- */

let reports = []

app.get('/reports', (req, res) => {
  res.send(JSON.stringify(reports))
})

app.post(
  '/.well-known/attribution-reporting/debug/report-event-attribution',
  async (req, res) => {
    console.log('DEBUG REPORT RECEIVED:', req.body)
    const newReport = { ...req.body, date: new Date() }
    reports = [newReport, ...reports]
    console.log(
      '\x1b[1;31m%s\x1b[0m',
      `🚀 Adtech has received a debug report from the browser`
    )
    res.sendStatus(200)
  }
)

app.post(
  '/.well-known/attribution-reporting/report-event-attribution',
  async (req, res) => {
    console.log('REGULAR REPORT RECEIVED:', req.body)
    const newReport = { ...req.body, date: new Date() }
    reports = [newReport, ...reports]
    console.log(
      '\x1b[1;31m%s\x1b[0m',
      `🚀 Adtech has received a report from the browser`
    )
    res.sendStatus(200)
  }
)

const listener = app.listen(process.env.PORT || PORT, () => {
  console.log(
    '\x1b[1;31m%s\x1b[0m',
    `🚀 Adtech server is listening on port ${listener.address().port}`
  )
})
<|endoftext|>