<commit_before>"use strict"

const functions = require('firebase-functions')
const admin = require("firebase-admin")
const _ = require("ramda")

admin.initializeApp(functions.config().firebase);

const second = 1000
const minute = 60 * second
const tenMinutes = 10 * minute
const fiveMinutes = 5 * minute

exports.testPush = functions.https.onRequest((req, res) => {
    // admin.database().ref('/messages').push({original: original}).then(snapshot => {
    //     // Redirect with 303 SEE OTHER to the URL of the pushed object in the Firebase console.
    //     res.redirect(303, snapshot.ref);
    // });
    return admin.database().ref("/users").once("value")
                .then(sendTestPushToAllUsersWithRegistrationToken)
                .then(arr => res.json(arr))

});

function sendTestPushToAllUsersWithRegistrationToken(userMap) {
    const promiseList = []
    userMap.forEach(function (userEntry) {
        const userId = userEntry.key
        const userData = userEntry.val()
        if (userData.token) {
            const promise = admin
                .messaging()
                .sendToDevice(
                    userData.token,
                    {
                        data: {
                            todoId: "7aIPoEclCGfR6lPUXb71hGXdoETwthsaETqSK98Bne2qyw2uWJcTgKDj03lpPCDt",
                            uid: userId,
                            timestamp: Date.now() + ""
                        }
                    },
                    {timeToLive: tenMinutes, priority: "high"}
                )
            promiseList.push(promise)
        }
    })
    return Promise.all(promiseList)
}

exports.notificationCorn = functions.https.onRequest((req, res) => {
    return admin
        .database().ref("/notifications")
        .orderByChild("timestamp")
        .endAt(Date.now() + fiveMinutes)
        .once("value")
        .then(sendPushNotifications)
        .then(arr => {
            console.log(arr)
            return res.json(arr)
        })
})

function getUserClients(uid) {
    return admin.database().ref("/users/" + uid + "/clients").once("value")
                .then(getMapFromSnapshot)
                .then(_.values)
}

function getMapFromSnapshot(snapshot) {
    const map = {}
    snapshot.forEach(entry => {
        map[entry.key] = entry.val()
    })
    return map;
}

function sendPushNotifications(notificationMap) {
    const getUserClientsMemo = _.memoize(getUserClients)
    const promiseList = []
    notificationMap.forEach(notificationEntry => {
        const notificationData = notificationEntry.val()
        const uid = notificationData.uid
        const clients = getUserClientsMemo(uid)
        promiseList.push(clients.then(sendNotificationToClients(notificationData)))
    })
    return Promise.all(promiseList)
}

const sendNotificationToClients = notificationData => clients => {
    console.log("clients", clients)
    const {todoId, timestamp, uid} = notificationData

    function sendPushForClient(client) {
        if (client.connected) {
            console
                .log("ignoring push for connected client"
                    , notificationData
                    , client)

            return Promise.resolve({
                msg: "ignoring push for connected client", client, notificationData
            })
        }

        return admin
            .messaging()
            .sendToDevice(
                client.token,
                {data: {todoId, timestamp: "" + timestamp, uid}},
                {timeToLive: tenMinutes, priority: "high"}
            )
            .then(mdRes => {
                const tokenUnregistered = mdRes.results.find(mdResult => {
                    return mdResult.error && mdResult.error.code === "messaging/registration-token-not-registered"
                })
                if (tokenUnregistered) {
                    return deleteToken(uid, client.id)
                        .then(res => ({error: {mdRes, deleteTokenRes: res}}))
                }
                return mdRes
            })
    }

    return Promise.all(_.map(sendPushForClient, clients))
}

function deleteToken(uid, deviceId) {
    return admin.database().ref(`/users/${uid}/clients/${deviceId}/token`).set(null)
}

const sendNotification = data =>
    getUserClients(data.uid)
        .then(sendNotificationToClients(data))

// exports.onTodoUpdated =
//     functions
//         .database.ref("/users/{uid}/todo-db/{todoId}")
//         .onWrite(event => {
//             const oldTodo = event.data.previous.val();
//             const newTodo = event.data.val()
//             const uid = event.params["uid"]
//             const todoId = event.params["todoId"]
//
//             if (oldTodo && newTodo) {
//                 // todo was updated
//                 if (oldTodo.dueAt && newTodo.dueAt
//                     && oldTodo.dueAt === newTodo.dueAt
//                     && oldTodo.reminder && oldTodo.reminder.at
//                     && newTodo.reminder && newTodo.reminder.at
//                     && oldTodo.reminder.at !== newTodo.reminder.at
//                 ) {
//                     const timestamp = oldTodo.reminder.at
//                     //todo was snoozed
//                     const notificationData = {uid, timestamp, todoId}
//                     return getUserClients(uid)
//                         .then(sendNotificationToClients(notificationData))
//                 }
//
//
//             }
//
//         })

const createRef = path => admin.database().ref(path)

const write = _.curry((value, refOrPath) => {
    const ref = _.when(_.is(String), createRef)(refOrPath)
    console.log("write: ref = ", ref.toString(), "value = ", value)
    return ref.set(value)
})

const writeAt = _.flip(write)

const removeAt = write(null)

const notificationPath = (uid, todoId) => `/notifications/${uid}---${todoId}`

const conditionalPromise = (fn, bool) => bool ? fn() : Promise.resolve()

function addNotification(uid, todoId, newTimestamp, shouldSendPush) {
    const writeNotification = writeAt(notificationPath(uid, todoId))
    const notificationData = {uid, todoId, newTimestamp}
    return conditionalPromise(_ => sendNotification(notificationData), shouldSendPush)
        .then(_ => writeNotification(notificationData))
}

const shouldDeleteNotification = (deltaSnapshot) => {
    const doneSnapshot = deltaSnapshot.child("done")
    const deletedSnapshot = deltaSnapshot.child("deleted")
    const timestampSnapShot = deltaSnapshot.child("reminder/at")
    return (doneSnapshot.changed() && doneSnapshot.val() === true)
           || (deletedSnapshot.changed() && doneSnapshot.val() === true)
           || (timestampSnapShot.changed() && timestampSnapShot.val() === null)
}

const shouldTriggerPush = deltaSnapshot => {
    const dueAtProp = "dueAt"
    return deltaSnapshot.previous.child(dueAtProp).exists()
           && !deltaSnapshot.child(dueAtProp).changed()
}
exports.updateNotificationOnTodoChanged =
    functions
        .database.ref("/users/{uid}/todo-db/{todoId}")
        .onWrite(event => {
            const deltaSnapshot = event.data
            const uid = event.params["uid"]
            const todoId = event.params["todoId"]

            console.log(deltaSnapshot.current.val(), deltaSnapshot.previous.val())

            if (shouldDeleteNotification(deltaSnapshot)) {
                return removeAt(notificationPath(uid, todoId))
            }

            const timestampSnapshot = deltaSnapshot.child("reminder/at")
            if (timestampSnapshot.changed()) {

                const shouldSendPush = shouldTriggerPush(deltaSnapshot)
                console.log("shouldSendPush", shouldSendPush)
                return addNotification(
                    uid, todoId,
                    timestampSnapshot.val(),
                    shouldSendPush
                )
            }
        })
<commit_msg>* firebase functions: merge both todo update triggers<commit_after>"use strict"

const functions = require('firebase-functions')
const admin = require("firebase-admin")
const _ = require("ramda")

admin.initializeApp(functions.config().firebase);

const second = 1000
const minute = 60 * second
const tenMinutes = 10 * minute
const fiveMinutes = 5 * minute

exports.testPush = functions.https.onRequest((req, res) => {
    // admin.database().ref('/messages').push({original: original}).then(snapshot => {
    //     // Redirect with 303 SEE OTHER to the URL of the pushed object in the Firebase console.
    //     res.redirect(303, snapshot.ref);
    // });
    return admin.database().ref("/users").once("value")
                .then(sendTestPushToAllUsersWithRegistrationToken)
                .then(arr => res.json(arr))

});

function sendTestPushToAllUsersWithRegistrationToken(userMap) {
    const promiseList = []
    userMap.forEach(function (userEntry) {
        const userId = userEntry.key
        const userData = userEntry.val()
        if (userData.token) {
            const promise = admin
                .messaging()
                .sendToDevice(
                    userData.token,
                    {
                        data: {
                            todoId: "7aIPoEclCGfR6lPUXb71hGXdoETwthsaETqSK98Bne2qyw2uWJcTgKDj03lpPCDt",
                            uid: userId,
                            timestamp: Date.now() + ""
                        }
                    },
                    {timeToLive: tenMinutes, priority: "high"}
                )
            promiseList.push(promise)
        }
    })
    return Promise.all(promiseList)
}

exports.notificationCorn = functions.https.onRequest((req, res) => {
    return admin
        .database().ref("/notifications")
        .orderByChild("timestamp")
        .endAt(Date.now() + fiveMinutes)
        .once("value")
        .then(sendPushNotifications)
        .then(arr => {
            console.log(arr)
            return res.json(arr)
        })
})

function getUserClients(uid) {
    return admin.database().ref("/users/" + uid + "/clients").once("value")
                .then(getMapFromSnapshot)
                .then(_.values)
}

function getMapFromSnapshot(snapshot) {
    const map = {}
    snapshot.forEach(entry => {
        map[entry.key] = entry.val()
    })
    return map;
}

function sendPushNotifications(notificationMap) {
    const getUserClientsMemo = _.memoize(getUserClients)
    const promiseList = []
    notificationMap.forEach(notificationEntry => {
        const notificationData = notificationEntry.val()
        const uid = notificationData.uid
        const clients = getUserClientsMemo(uid)
        promiseList.push(clients.then(sendNotificationToClients(notificationData)))
    })
    return Promise.all(promiseList)
}

const sendNotificationToClients = notificationData => clients => {
    console.log("clients", clients)
    const {todoId, timestamp, uid} = notificationData

    function sendPushForClient(client) {
        if (client.connected) {
            console
                .log("ignoring push for connected client"
                    , notificationData
                    , client)

            return Promise.resolve({
                msg: "ignoring push for connected client", client, notificationData
            })
        }

        return admin
            .messaging()
            .sendToDevice(
                client.token,
                {data: {todoId, timestamp: "" + timestamp, uid}},
                {timeToLive: tenMinutes, priority: "high"}
            )
            .then(mdRes => {
                const tokenUnregistered = mdRes.results.find(mdResult => {
                    return mdResult.error && mdResult.error.code === "messaging/registration-token-not-registered"
                })
                if (tokenUnregistered) {
                    return deleteToken(uid, client.id)
                        .then(res => ({error: {mdRes, deleteTokenRes: res}}))
                }
                return mdRes
            })
    }

    return Promise.all(_.map(sendPushForClient, clients))
}

function deleteToken(uid, deviceId) {
    return admin.database().ref(`/users/${uid}/clients/${deviceId}/token`).set(null)
}

const sendNotification = data =>
    getUserClients(data.uid)
        .then(sendNotificationToClients(data))

// exports.onTodoUpdated =
//     functions
//         .database.ref("/users/{uid}/todo-db/{todoId}")
//         .onWrite(event => {
//             const oldTodo = event.data.previous.val();
//             const newTodo = event.data.val()
//             const uid = event.params["uid"]
//             const todoId = event.params["todoId"]
//
//             if (oldTodo && newTodo) {
//                 // todo was updated
//                 if (oldTodo.dueAt && newTodo.dueAt
//                     && oldTodo.dueAt === newTodo.dueAt
//                     && oldTodo.reminder && oldTodo.reminder.at
//                     && newTodo.reminder && newTodo.reminder.at
//                     && oldTodo.reminder.at !== newTodo.reminder.at
//                 ) {
//                     const timestamp = oldTodo.reminder.at
//                     //todo was snoozed
//                     const notificationData = {uid, timestamp, todoId}
//                     return getUserClients(uid)
//                         .then(sendNotificationToClients(notificationData))
//                 }
//
//
//             }
//
//         })

const createRef = path => admin.database().ref(path)

const write = _.curry((value, refOrPath) => {
    const ref = _.when(_.is(String), createRef)(refOrPath)
    console.log("write: ref = ", ref.toString(), "value = ", value)
    return ref.set(value)
})

const writeAt = _.flip(write)

const removeAt = write(null)

const notificationPath = (uid, todoId) => `/notifications/${uid}---${todoId}`

const conditionalPromise = (fn, bool) => bool ? fn() : Promise.resolve()

function addNotification(uid, todoId, newTimestamp, shouldSendPush) {
    const writeNotification = writeAt(notificationPath(uid, todoId))
    const notificationData = {uid, todoId, newTimestamp}
    return conditionalPromise(_ => sendNotification(notificationData), shouldSendPush)
        .then(_ => writeNotification(notificationData))
}

const shouldDeleteNotification = (deltaSnapshot) => {
    const doneSnapshot = deltaSnapshot.child("done")
    const deletedSnapshot = deltaSnapshot.child("deleted")
    const timestampSnapShot = deltaSnapshot.child("reminder/at")
    return (doneSnapshot.changed() && doneSnapshot.val() === true)
           || (deletedSnapshot.changed() && doneSnapshot.val() === true)
           || (timestampSnapShot.changed() && timestampSnapShot.val() === null)
}

const shouldTriggerPush = deltaSnapshot => {
    const dueAtProp = "dueAt"
    return deltaSnapshot.previous.child(dueAtProp).exists()
           && !deltaSnapshot.child(dueAtProp).changed()
}
exports.updateNotificationOnTodoChanged =
    functions
        .database.ref("/users/{uid}/todo-db/{todoId}")
        .onWrite(event => {
            const deltaSnapshot = event.data
            const uid = event.params["uid"]
            const todoId = event.params["todoId"]

            console.log(deltaSnapshot.current.val(), deltaSnapshot.previous.val())

            if (shouldDeleteNotification(deltaSnapshot)) {
                return removeAt(notificationPath(uid, todoId))
            }

            const timestampSnapshot = deltaSnapshot.child("reminder/at")
            if (timestampSnapshot.changed()) {

                const shouldSendPush = shouldTriggerPush(deltaSnapshot)
                console.log("shouldSendPush", shouldSendPush)
                return addNotification(
                    uid, todoId,
                    timestampSnapshot.val(),
                    shouldSendPush
                )
            }
            console.log("No notification changes to process, returning")
        })
<|endoftext|>


<commit_before>var crypto = require('crypto');
var base32 = require('thirty-two');
var QR = require('qr-image');

var TFA = module.exports = {};

// get a base36 crypto secure key
    /**
     * @description
     * Generates the key which can be used to make a QR code for the user to add to their authenticator apps and check codes against.
     * @param length The length desired for the key.
     * @returns A key you can store, check codes and generate QR codes from.
     * @example
     * tfa.generateKey(32, function(err, key) {
     * //Store key and generate a QR code so users can add it to their authenticator app.
     * })
      */
TFA.generateKey = function(length, cb) {
  if (!cb && typeof length === 'function') {
    cb = length;
    length = 20;
  }

  var key = '';
  var get = function() {
    // 7 bytes (14 char) is the max JS can handle with precision
    // using 6 to be on the safe side eh (nobody trusts JS numbers)
    crypto.randomBytes(6, function(err, bytes) {
      if (err) return cb(err);
      key += parseInt(bytes.toString('hex'), 16).toString(36);
      if (key.length < length) return get();
      cb(err, key.slice(0, length));
    });
  };
  get();
};

    /**
     * @description
     * Verify a users HOTP code they entered against the given key
     * @param key  The key to check the HOTP code against
     * @param code The code entered by the user from the authenticator app.
     * @param counter The time for verifying the code against the key.
     * @returns Either true or false depending on if the code is valid or not.
     * @example
     * let counter = Math.floor(Date.now() / 1000 / 30);
     * let validCode = tfa.veryifyHOTP(key, code, counter);
     * if (validCode == true) {
     * //Log user in
     * } else {
     * //Display erorr message
     * }
     */
TFA.verifyHOTP = function(key, code, counter, opts) {
  opts = opts || {};

  var drift = (opts.drift || 0) / 2;

  // allow drift X counters before
  var before = opts.beforeDrift || drift;

  // allow drift X counters after
  var after = opts.afterDrift || drift;

  for (var i = counter - before; i <= counter + after; i++) {
    if (TFA.generateCode(key, i, opts) === code) return true;
  }

  return false;
};

    /**
     * @description
     * Verify a users TOTP code they entered against the given key
     * @param key  The key to check the TOTP code against
     * @param code The code entered by the user from the authenticator app.
     * @returns Either true or false depending on if the code is valid or not.
     * @example
     * let validCode = tfa.verifyTOTP(key, code);
     * if (validCode == true) {
     * //Log user in
     * } else {
     * //Display erorr message
     * }
     */
TFA.verifyTOTP = function(key, code, opts) {
  opts = opts || {};

  var step = opts.step || 30;

  var counter = Math.floor(Date.now() / 1000 / step);

  return TFA.verifyHOTP(key, code, counter, opts);
};

TFA.generateCode = function(key, counter, opts) {
  opts = opts || {};
  var length = opts.length || 6;

  var hmac = crypto.createHmac('sha1', key);

  // get the counter as bytes
  var counterBytes = new Array(8);
  for (var i = counterBytes.length - 1; i >= 0; i--) {
    counterBytes[i] = counter & 0xff;
    counter = counter >> 8;
  }

  var token = hmac.update(new Buffer(counterBytes)).digest('hex');

  // get the token as bytes
  var tokenBytes = [];
  for (var i = 0; i < token.length; i += 2) {
    tokenBytes.push(parseInt(token.substr(i, 2), 16));
  }

  // truncate to 4 bytes
  var offset = tokenBytes[19] & 0xf;
  var ourCode =
    (tokenBytes[offset++] & 0x7f) << 24 |
    (tokenBytes[offset++] & 0xff) << 16 |
    (tokenBytes[offset++] & 0xff) << 8  |
    (tokenBytes[offset++] & 0xff);

  // we want strings!
  ourCode += '';

  // truncate to correct length
  ourCode = ourCode.substr(ourCode.length - length);

  // 0 pad
  while (ourCode.length < length) ourCode = '0' + ourCode;

  return ourCode;
};

    /**
     * @description
     * Converts a key to a format which can be pasted into an authenticator app.
     * @param key  The key to convert
     * @returns The authenticater-friendly code.
     * @example
     * let multikey = tfa.base32Encode(key);
     * console.log("You can manually enter this code into the authenticator app: " & multikey)
     */
TFA.base32Encode = function (key) {
  return base32.encode(key).toString().replace(/=/g, '');
};

TFA.generateUrl = function (name, account, key) {
  return 'otpauth://totp/' + encodeURIComponent(account)
           + '?issuer=' + encodeURIComponent(name)
           + '&secret=' + TFA.base32Encode(key);
};

    /**
     * @description
     * Generates a QR code that can be scanned to add the account to an authenciator app.
     * @param name The name to be listed on the authenticator app- usually the company or product name.
     * @param account The account to be listed on the authenticator app- Usually the users email.
     * @param key The key generated for the users multifactor.
     * @returns A base64 string with the image.
     * @example
     * tfa.generateGoogleQR('Company', 'john.doe@example.com', key, function(err, qr) {
     * //Render a page with the QR code for the user to scan.
     * })
     */
TFA.generateGoogleQR = function(name, account, key, opts, cb) {
  if (!cb && typeof opts === 'function') {
    cb = opts;
    opts = {};
  }

  var data = TFA.generateUrl(name, account, key);

  var formatter = function(buf) {
    switch(opts.encoding) {
      case 'buffer':
        return buf;
      case 'base64':
        return buf.toString('base64');
      case 'data': default:
        return 'data:image/png;base64,' + buf.toString('base64')
    }
  };

  var qrOpts = { type: 'png' };
  for (var i in opts) qrOpts[i] = opts[i];

  var pngStream = QR.image(data, qrOpts);

  var pngData = [];
  pngStream.on('data', function(d) { pngData.push(d); });
  pngStream.on('end', function() {
    var png = Buffer.concat(pngData);
    cb(null, formatter(png));
  });
};

    /**
     * @description
     * Generates codes which can be checked against and used if the authenticator app is not available.
     * @param count The number of backup keys you want to generate
     * @param pattern The pattern you want the codes to be generated in
     * @returns An array of backup codes you can store and show the user
     * @example
     * tfa.generateBackupCodes(6, 'xxxx-xxxx-xxxx', function(err, codes) {
     * //Show and store the backup keys to the user.
     * })
     */
TFA.generateBackupCodes = function(count, pattern, cb) {
  if (!cb && typeof pattern === 'function') {
    cb = pattern;
    pattern = 'xxxx-xxxx-xxxx';
  }

  var codes = [];
  for (var c = 0; c < count; c++) {
    TFA.generateBackupCode(pattern, function(err, code) {
      if (err) {
        cb(err);
        cb = function(){};
        return;
      }

      codes.push(code);
      if (codes.length === count) cb(err, codes);
    });
  }
};

TFA.generateBackupCode = function(pattern, cb) {
  if (!cb && typeof pattern === 'function') {
    cb = pattern;
    pattern = 'xxxx-xxxx-xxxx';
  }

  // how many crypto bytes do we need?
  var patternLength = Math.ceil((pattern.split('x').length) - 1 / 2);

  crypto.randomBytes(patternLength, function(err, buf) {
    if (err) return cb(err);
    var chars = buf.toString('hex');
    var code = '';

    // number of crypto characters that we've used
    var xs = 0;
    for (var i = 0; i < pattern.length; i++) {
      code += pattern[i] === 'x' ? chars[xs++] : pattern[i];
    }
    cb(err, code);
  });
}
<commit_msg>Change documentation according to feedback.<commit_after>var crypto = require("crypto");
var base32 = require("thirty-two");
var QR = require("qr-image");

var TFA = (module.exports = {});

/**
 * @description
 * Generates the key which can be used to make a QR code for the user to add to their authenticator apps and check codes against.
 * @param length The length desired for the key.
 * @param cb A callback function you wish to pass the results of the function to.
 * @example
 * tfa.generateKey(32, function(err, key) {
 * //Store key and generate a QR code so users can add it to their authenticator app.
 * })
 */
TFA.generateKey = function(length, cb) {
  if (!cb && typeof length === "function") {
    cb = length;
    length = 20;
  }

  var key = "";
  var get = function() {
    // 7 bytes (14 char) is the max JS can handle with precision
    // using 6 to be on the safe side eh (nobody trusts JS numbers)
    crypto.randomBytes(6, function(err, bytes) {
      if (err) return cb(err);
      key += parseInt(bytes.toString("hex"), 16).toString(36);
      if (key.length < length) return get();
      cb(err, key.slice(0, length));
    });
  };
  get();
};

/**
 * @description
 * Verify a users HOTP code they entered against the given key
 * @param key  The key to check the HOTP code against
 * @param code The code entered by the user from the authenticator app.
 * @param counter The time for verifying the code against the key.
 * @param opts An optional parameter for specifying drift options.
 * @returns Either true or false depending on if the code is valid or not.
 * @example
 * var counter = Math.floor(Date.now() / 1000 / 30);
 * var validCode = tfa.verifyHOTP(key, code, counter);
 * if (validCode == true) {
 * //Log user in
 * } else {
 * //Display error message
 * }
 */
TFA.verifyHOTP = function(key, code, counter, opts) {
  opts = opts || {};

  var drift = (opts.drift || 0) / 2;

  // allow drift X counters before
  var before = opts.beforeDrift || drift;

  // allow drift X counters after
  var after = opts.afterDrift || drift;

  for (var i = counter - before; i <= counter + after; i++) {
    if (TFA.generateCode(key, i, opts) === code) return true;
  }

  return false;
};

/**
 * @description
 * Verify a users TOTP code they entered against the given key
 * @param key  The key to check the TOTP code against
 * @param code The code entered by the user from the authenticator app.
 * @returns Either true or false depending on if the code is valid or not.
 * @example
 * var validCode = tfa.verifyTOTP(key, code);
 * if (validCode == true) {
 * //Log user in
 * } else {
 * //Display error message
 * }
 */
TFA.verifyTOTP = function(key, code, opts) {
  opts = opts || {};

  var step = opts.step || 30;

  var counter = Math.floor(Date.now() / 1000 / step);

  return TFA.verifyHOTP(key, code, counter, opts);
};

TFA.generateCode = function(key, counter, opts) {
  opts = opts || {};
  var length = opts.length || 6;

  var hmac = crypto.createHmac("sha1", key);

  // get the counter as bytes
  var counterBytes = new Array(8);
  for (var i = counterBytes.length - 1; i >= 0; i--) {
    counterBytes[i] = counter & 0xff;
    counter = counter >> 8;
  }

  var token = hmac.update(new Buffer(counterBytes)).digest("hex");

  // get the token as bytes
  var tokenBytes = [];
  for (var i = 0; i < token.length; i += 2) {
    tokenBytes.push(parseInt(token.substr(i, 2), 16));
  }

  // truncate to 4 bytes
  var offset = tokenBytes[19] & 0xf;
  var ourCode =
    ((tokenBytes[offset++] & 0x7f) << 24) |
    ((tokenBytes[offset++] & 0xff) << 16) |
    ((tokenBytes[offset++] & 0xff) << 8) |
    (tokenBytes[offset++] & 0xff);

  // we want strings!
  ourCode += "";

  // truncate to correct length
  ourCode = ourCode.substr(ourCode.length - length);

  // 0 pad
  while (ourCode.length < length) ourCode = "0" + ourCode;

  return ourCode;
};

/**
 * @description
 * Converts a key to a format which can be pasted into an authenticator app.
 * @param key  The key to convert
 * @returns The authenticater-friendly code.
 * @example
 * var multikey = tfa.base32Encode(key);
 * console.log('You can manually enter this code into the authenticator app: ' + multikey)
 */
TFA.base32Encode = function(key) {
  return base32
    .encode(key)
    .toString()
    .replace(/=/g, "");
};

TFA.generateUrl = function(name, account, key) {
  return (
    "otpauth://totp/" +
    encodeURIComponent(account) +
    "?issuer=" +
    encodeURIComponent(name) +
    "&secret=" +
    TFA.base32Encode(key)
  );
};

/**
 * @description
 * Generates a QR code that can be scanned to add the account to an authenciator app.
 * @param name The name to be listed on the authenticator app- usually the company or product name.
 * @param account The account to be listed on the authenticator app- Usually the users email.
 * @param key The key generated for the users multifactor.
 * @param opts An optional parameter for specifying drift options.
 * @returns A base64 string with the image.
 * @example
 * tfa.generateGoogleQR('Company', 'john.doe@example.com', key, function(err, qr) {
 * //Render a page with the QR code for the user to scan.
 * })
 */
TFA.generateGoogleQR = function(name, account, key, opts, cb) {
  if (!cb && typeof opts === "function") {
    cb = opts;
    opts = {};
  }

  var data = TFA.generateUrl(name, account, key);

  var formatter = function(buf) {
    switch (opts.encoding) {
      case "buffer":
        return buf;
      case "base64":
        return buf.toString("base64");
      case "data":
      default:
        return "data:image/png;base64," + buf.toString("base64");
    }
  };

  var qrOpts = { type: "png" };
  for (var i in opts) qrOpts[i] = opts[i];

  var pngStream = QR.image(data, qrOpts);

  var pngData = [];
  pngStream.on("data", function(d) {
    pngData.push(d);
  });
  pngStream.on("end", function() {
    var png = Buffer.concat(pngData);
    cb(null, formatter(png));
  });
};

/**
 * @description
 * Generates codes which can be checked against and used if the authenticator app is not available.
 * @param count The number of backup keys you want to generate
 * @param pattern An optional parameter for the pattern you want the codes to be generated in
 * @param cb A callback function you wish to pass the results of the function to.
 * @example
 * tfa.generateBackupCodes(6, 'xxxx-xxxx-xxxx', function(err, codes) {
 * //Show and store the backup keys to the user.
 * })
 */
TFA.generateBackupCodes = function(count, pattern, cb) {
  if (!cb && typeof pattern === "function") {
    cb = pattern;
    pattern = "xxxx-xxxx-xxxx";
  }

  var codes = [];
  for (var c = 0; c < count; c++) {
    TFA.generateBackupCode(pattern, function(err, code) {
      if (err) {
        cb(err);
        cb = function() {};
        return;
      }

      codes.push(code);
      if (codes.length === count) cb(err, codes);
    });
  }
};

TFA.generateBackupCode = function(pattern, cb) {
  if (!cb && typeof pattern === "function") {
    cb = pattern;
    pattern = "xxxx-xxxx-xxxx";
  }

  // how many crypto bytes do we need?
  var patternLength = Math.ceil(pattern.split("x").length - 1 / 2);

  crypto.randomBytes(patternLength, function(err, buf) {
    if (err) return cb(err);
    var chars = buf.toString("hex");
    var code = "";

    // number of crypto characters that we've used
    var xs = 0;
    for (var i = 0; i < pattern.length; i++) {
      code += pattern[i] === "x" ? chars[xs++] : pattern[i];
    }
    cb(err, code);
  });
};
<|endoftext|>


<commit_before>/* Javascript for StaffGradedAssignmentXBlock. */
function StaffGradedAssignmentXBlock(runtime, element)
{
    function xblock($, _)
    {
        var uploadUrl = runtime.handlerUrl(element, 'student_upload_file');
        var studentDownloadUrl = runtime.handlerUrl(element, 'student_download_file');
        var studentDownloadZippedUrl = runtime.handlerUrl(element, 'student_download_zipped')
        var submitUrl = runtime.handlerUrl(element, 'student_submit');
        var deleteSubmissionFileUrl = runtime.handlerUrl(element, 'student_delete_file')

        var staffDownloadUrl = runtime.handlerUrl(element, 'staff_download_file');
        var staffDownloadZippedUrl = runtime.handlerUrl(element, 'staff_download_zipped');

        var annotatedUploadUrl = runtime.handlerUrl(element, 'staff_upload_annotated');
        var studentAnnotationDownloadUrl = runtime.handlerUrl(element, 'student_download_annotated');
        var studentAnnotationDownloadZippedUrl = runtime.handlerUrl(element, 'student_download_annotated_zipped');
        var deleteAnnotationFileUrl = runtime.handlerUrl(element, 'staff_delete_annotated');

        var staffDownloadAnnotatedUrl = runtime.handlerUrl(element, 'staff_download_annotated');
        var staffDownloadAnnotatedZippedUrl = runtime.handlerUrl(element, 'staff_download_annotated_zipped');

        var getStaffGradingUrl = runtime.handlerUrl(element, 'get_staff_grading_data');
        var enterGradeUrl = runtime.handlerUrl(element, 'staff_enter_grade');
        var removeGradeUrl = runtime.handlerUrl(element, 'staff_remove_grade');

        var reopenSubmissionUrl = runtime.handlerUrl(element, 'staff_reopen_submission');
        var removeSubmissionUrl = runtime.handlerUrl(element, 'staff_remove_submission')
        var reopenAllSubmissionsUrl = runtime.handlerUrl(element, 'staff_reopen_all_submissions');
        var removeAllSubmissionsUrl = runtime.handlerUrl(element, 'staff_remove_all_submissions');

        var template = _.template($(element).find("#sga-tmpl").text());
        var uploadTemplate = _.template($(element).find("#sga-upload-tmpl").text());
        var filelistTemplate = _.template($(element).find("#sga-filelist-tmpl").text());
        var gradingTemplate;

        function render(state)
        {
            // Add download urls to template context
            //state.downloadUrl = studentDownloadUrl;
            //state.downloadZippedUrl = studentDownloadZippedUrl;
            //state.downloadAnnotatedUrl = studentAnnotationDownloadUrl;
            //state.downloadAnnotatedZippedUrl = studentAnootationDownloadZippedUrl;
            state.error = state.error ? state.error : false;

            // Render template
            var content = $(element).find("#sga-content").html(template(state));

            var uploadData = {
                filelist:          state.uploaded,
                uploadUrl:         uploadUrl,
                downloadZippedUrl: studentDownloadZippedUrl,
                downloadUrl: function(hash)
                {
                    return studentDownloadUrl + '/' + hash;
                },
                deleteUrl: function(hash)
                {
                    return deleteSubmissionFileUrl +'/' + hash;
                },
                upload_allowed:    state.upload_allowed
            };

            var annotatedData = {
                filelist:          state.annotated,
                downloadZippedUrl: studentAnnotationDownloadZippedUrl,
                downloadUrl: function(hash)
                {
                    return studentDownloadUrl + '/' + hash;
                },
                deleteUrl: function(hash)
                {
                    return "";
                },
                upload_allowed:    false
            };

            handleUpload($('#student-upload'), uploadData);
            handleFilelist($('#student-annotated'), annotatedData);

            //submit assignment for marking.
            $(content).find(".assingmentsubmit").click(function(e)
            {
                $.get(submitUrl).success(function () {
                    state.submitted = true;
                    state.upload_allowed = false;
                    render(state);
                });
            });
        }

        function renderStaffGrading(data)
        {
            $(".grade-modal").hide();
            //$(".annotated-modal").hide();

            var allStudentData = data;

            // Add download urls to template context
            //data.downloadUrl = staffDownloadUrl;
            //data.downloadZippedUrl = staffDownloadZippedUrl;
            //data.reopenSubmissionUrl = reopenSubmissionUrl;
            //data.removeSubmissionUrl = removeSubmissionUrl;

            // Render template
            $(element).find("#grade-info")
                .html(gradingTemplate(data))
                .data(data);

            // Map data to table rows
            data.assignments.map(function(assignment) {
                $(element).find("#grade-info #row-" + assignment.module_id)
                    .data(assignment);
            });

            // Set up grade entry modal
            $(element).find(".enter-grade-button")
                .leanModal({closeButton: "#enter-grade-cancel"})
                .on("click", handleGradeEntry);

            //set up annotated file submision modal
            $(element).find(".manage-annotated-button")
                .leanModal({closeButton: "#manage-annotated-exit"})
                .on("click", handleManageAnnotated);

            //all submission control
            $(element).find(".remove-all-submissions-button")
                .on("click", function()
                {
                    var url = removeAllSubmissionsUrl;

                    $.get(url).success(function() {
                        allStudentData.assignments.each(
                            removeSubmission(this)
                        );

                        renderStaffGrading(allStudentData);
                    });
                });

            //reopen all submissions for the asingment.
            $(element).find(".reopen-all-submissions-button")
                .on("click", function()
                {
                    var url = reopenAllSubmissionsUrl;

                    $.get(url).success(function() {
                        allStudentData.assignments.each(function() {
                            this.submitted = false;
                        });

                        renderStaffGrading(allStudentData);
                    });
                });

            //Remove a submission, including grades and files.
            $(element).find(".remove-submission-button")
                .on("click", function()
                {
                    var module_id = $(this).parents("tr").data("module_id");
                    var url = removeSubmissionUrl + "?module_id=" + module_id;

                    $.get(url).success(function() {
                        removeSubmission($.grep(allStudentData.assignments, function(e) {
                            return e.module_id == module_id;
                        })[0]);

                        renderStaffGrading(allStudentData);
                    });
                });

            //reopens a submission for a student.  Clears previous grade.
            $(element).find(".reopen-submission-button")
                .on("click", function()
                {
                    var module_id = $(this).parents("tr").data("module_id");
                    var url = reopenSubmissionUrl + "?module_id=" + module_id;

                    $.get(url).success(function() {
                        $.grep(allStudentData.assignments, function(e) {
                            return e.module_id == module_id;
                        })[0].submitted = false;

                        renderStaffGrading(allStudentData);
                    });
                });

            //All upload, download and delete for annotated files
            function handleManageAnnotated()
            {
                var row = $(this).parents("tr");
                var module_id = row.data("module_id")

                var studentData =
                    $.grep(allStudentData.assignments, function(e){
                        return e.module_id == module_id;
                    })[0];

                $('#student-name-annotations').text(studentData.fullname);

                //Object containing data needed for rendering file list
                //and upload button.
                var fileData = {
                    filelist:          studentData.annotated,
                    uploadUrl:         annotatedUploadUrl + "?module_id="
                                       + studentData.module_id,
                    downloadZippedUrl: staffDownloadAnnotatedZippedUrl
                                       + "?module_id=" + studentData.module_id,
                    downloadUrl: function(hash)
                    {
                        return staffDownloadAnnotatedUrl + '/' + hash
                        + "?module_id=" + studentData.module_id;
                    },
                    deleteUrl: function(hash)
                    {
                        return deleteAnnotationFileUrl +'/' + hash
                        + "?module_id=" + studentData.module_id;
                    },
                    upload_allowed:    true
                };

                var form = $(element).find("#manage-annotations-form");

                handleUpload(form, fileData);

                form.find("#manage-annotated-exit").on("click", function() {
                    setTimeout(function() {
                        $("#grade-submissions-button").click();
                    }, 225);
                });
            }

            /* Click event handler for "enter grade" */
            function handleGradeEntry()
            {
                var row = $(this).parents("tr");
                var module_id = row.data("module_id")

                var studentData =
                    $.grep(allStudentData.assignments, function(e){
                        return e.module_id == module_id;
                    })[0];

                var form = $(element).find("#enter-grade-form");
                $(element).find("#student-name").text(row.data("fullname"));

                form.find("#module_id-input").val(studentData.module_id);
                form.find("#grade-input").val(studentData.score);
                form.find("#comment-input").text(studentData.comment);

                form.off("submit").on("submit", function(event) {
                    var max_score = row.parents("#grade-info").data("max_score");
                    var score = Number(form.find("#grade-input").val());
                    event.preventDefault();

                    if (isNaN(score)) {
                        form.find(".error").html("
Grade must be a number.");
                    }
                    else if (score < 0) {
                        form.find(".error").html("
Grade must be positive.");
                    }
                    else if (score > max_score) {
                        form.find(".error").html("
Maximum score is " + max_score);
                    }
                    else {
                        // No errors
                        studentData.score = score;
                        studentData.comment = form.find("#comment-input").text();

                        $.post(enterGradeUrl, form.serialize())
                            .success(function() {
                                renderStaffGrading(allStudentData);
                            });
                    }
                });
                form.find("#remove-grade").on("click", function() {
                    var url = removeGradeUrl + "?module_id=" + module_id;
                    $.get(url).success(renderStaffGrading);
                });
                form.find("#enter-grade-cancel").on("click", function() {
                    /* We're kind of stretching the limits of leanModal, here,
                     * by nesting modals one on top of the other.  One side effect
                     * is that when the enter grade modal is closed, it hides
                     * the overlay for itself and for the staff grading modal,
                     * so the overlay is no longer present to click on to close
                     * the staff grading modal.  Since leanModal uses a fade out
                     * time of 200ms to hide the overlay, our work around is to
                     * wait 225ms and then just "click" the 'Grade Submissions'
                     * button again.  It would also probably be pretty
                     * straightforward to submit a patch to leanModal so that it
                     * would work properly with nested modals.
                     *
                     * See: https://github.com/mitodl/edx-sga/issues/13
                     */
                    setTimeout(function() {
                        $("#grade-submissions-button").click();
                    }, 225);
                });
            }
        }

        //reset a submission, removing all files and grades.
        function removeSubmission(submission)
        {
            submission.uploaded = [];
            submission.annotated = [];
            submision.submitted = false;

            removeGrade(submission);
        }

        //remove a grade from a submission
        function removeGrade(submission)
        {
            sumission.score = null;
            submission.comment = '';
            submision.published = false;
            submission.approved = false;
        }

        function handleUpload(parent, uploadState)
        {
            //look into removing
            //var uploadState = data;
            if (typeof uploadState.error === 'undefined')
            {
                uploadState.error = "";
            }

            //var parent = e;
            var fileUploadDiv = parent.find('.upload');

            var renderFileList = function(element)
            {
                return function(){
                    handleFilelist(element, uploadState);
                };
            }(parent.find('.filelist'));

            renderFileList();
            fileUploadDiv.html(uploadTemplate(uploadState));

            fileUploadDiv.find(".fileupload").fileupload({
                url: uploadState.uploadUrl,
                add: function(e, data)
                {
                    var do_upload = fileUploadDiv.html('');
                    $('<button/>')
                        .text('Upload ' + data.files[0].name)
                        .appendTo(do_upload)
                        .click(function() {
                            do_upload.text("Uploading...");
                            data.submit();
                        });
                },
                progressall: function(e, data)
                {
                    var percent = parseInt(data.loaded / data.total * 100, 10);
                    fileUploadDiv.text("Uploading... " + percent + "%");
                },
                fail: function(e, data)
                {
                    var error = "";
                    if (data.jqXHR.status == 413)
                    {
                        uploadState.error = "The file you are trying to upload is too large."
                    }
                    else
                    {
                        // Suitably vague
                        uploadState.error = "There was an error uploading your file.";

                        console.log("There was an error with file upload.");
                        console.log("event: ", e);
                        console.log("data: ", data);
                    }
                    handleUpload(parent, uploadState);
                },
                done: function(e, data)
                {
                    if (data.result.success !== undefined)
                    {
                        // Actually, this is an error
                        uploadState.error = data.result.success;
                    }
                    else
                    {
                        // The happy path, no errors
                        uploadState.filelist.push(data.result);
                        uploadState.error = "";
                    }
                    //reset the upload field.
                    handleUpload(parent, uploadState);

                }
            });
        }

        function handleFilelist(fileListDiv, fileState)
        {
            //look into removing
            //var fileState = data;

            //var fileListDiv = e;
            fileListDiv.html(filelistTemplate(fileState));

            fileListDiv.find(".fileDownload").each(function() {
                var pos = $(this).attr('value');
                url = fileState.downloadUrl(fileState.filelist[pos].sha1);
                $(this).attr("href", url);
            });

            fileListDiv.find(".fileDelete").on("click", function() {
                var url = fileState.deleteUrl(fileState.filelist[this.value].sha1);
                var pos = this.value;
                $.get(url).success(function(data) {
                    if (pos < fileState.filelist.length)
                    {
                        fileState.filelist.splice(pos, 1);
                    }

                    handleFilelist(fileListDiv, fileState)
                });
            });
        }

        $(function($) { // onLoad
            var block = $(element).find(".sga-block");
            var state = block.attr("data-state");
            render(JSON.parse(state));

            var is_staff = block.attr("data-staff") == "True";
            if (is_staff) {
                gradingTemplate = _.template(
                    $(element).find("#sga-grading-tmpl").text());

                block.find("#grade-submissions-button")
                    .leanModal()
                    .on("click", function() {
                        $.ajax({
                            url: getStaffGradingUrl,
                            success: renderStaffGrading
                        });
                    });
                block.find("#staff-debug-info-button")
                    .leanModal();
            }
        });
    }

    if (require === undefined) {
        /**
         * The LMS does not use require.js (although it loads it...) and
         * does not already load jquery.fileupload.  (It looks like it uses
         * jquery.ajaxfileupload instead.  But our XBlock uses
         * jquery.fileupload.
         */
        function loadjs(url) {
            $("


<commit_before>"use strict"

const functions = require('firebase-functions')
const express = require('express')
const generate = require('./generate.js')
const database = require('./database.js')
const app = express()

const admin = require('firebase-admin')
admin.initializeApp({
	credential: admin.credential.applicationDefault(),
	databaseURL: 'https://app-localization-2f645.firebaseio.com'
})

app.get('/export/:language/android', (request, response) =>
{
	generate.exportFile(request, response, admin, 'strings-{language}.xml', generate.android)
})

app.get('/export/:language/ios', (request, response) =>
{
	generate.exportFile(request, response, admin, 'Localizable-{language}.strings', generate.ios)
})

app.get('/export/:language/xliff', (request, response) =>
{
	generate.exportFile(request, response, admin, '{language}.xlf', generate.xliff)
})

app.get('/export/:language/json', (request, response) =>
{
	generate.exportFile(request, response, admin, '{language}.json', generate.json)
})

app.get('/export/:language/yaml', (request, response) =>
{
	generate.exportFile(request, response, admin, '{language}.yaml', generate.yaml)
})

const api = express()
api.use('/api', app)

exports.api = functions.https.onRequest(api)

// https://github.com/firebase/functions-samples
// https://us-central1-app-localization-2f645.cloudfunctions.net/[function]

exports.onTranslationUpdated = functions.database.ref('/segments/{segmentId}/translations/{translationId}/value').onUpdate(event =>
{
	const entry = {
		value: event.data.previous.val(),
		author: event.auth.variable.email,
		date: Date.now()
	}

	return event.data.ref.parent.child('history').push(entry)
})

exports.onLanguageAdded = functions.database.ref('/languages/{languageId}').onCreate(event =>
{
	console.log('a')

	database.segmentsRef(admin).once('value', snap =>
	{
		console.log('b')

		snap.forEach(function(entry)
		{
			const value = {
				value: '',
				validated: false
			}

			console.log('c')

			database.ref(`/segments/${entry.key}/translations`).push(value)
		})
	})
})<commit_msg>Fixed function call<commit_after>"use strict"

const functions = require('firebase-functions')
const express = require('express')
const generate = require('./generate.js')
const database = require('./database.js')
const app = express()

const admin = require('firebase-admin')
admin.initializeApp({
	credential: admin.credential.applicationDefault(),
	databaseURL: 'https://app-localization-2f645.firebaseio.com'
})

app.get('/export/:language/android', (request, response) =>
{
	generate.exportFile(request, response, admin, 'strings-{language}.xml', generate.android)
})

app.get('/export/:language/ios', (request, response) =>
{
	generate.exportFile(request, response, admin, 'Localizable-{language}.strings', generate.ios)
})

app.get('/export/:language/xliff', (request, response) =>
{
	generate.exportFile(request, response, admin, '{language}.xlf', generate.xliff)
})

app.get('/export/:language/json', (request, response) =>
{
	generate.exportFile(request, response, admin, '{language}.json', generate.json)
})

app.get('/export/:language/yaml', (request, response) =>
{
	generate.exportFile(request, response, admin, '{language}.yaml', generate.yaml)
})

const api = express()
api.use('/api', app)

exports.api = functions.https.onRequest(api)

// https://github.com/firebase/functions-samples
// https://us-central1-app-localization-2f645.cloudfunctions.net/[function]

exports.onTranslationUpdated = functions.database.ref('/segments/{segmentId}/translations/{translationId}/value').onUpdate(event =>
{
	const entry = {
		value: event.data.previous.val(),
		author: event.auth.variable.email,
		date: Date.now()
	}

	return event.data.ref.parent.child('history').push(entry)
})

exports.onLanguageAdded = functions.database.ref('/languages/{languageId}').onCreate(event =>
{
	console.log('a')

	database.segmentsRef(admin).once('value', snap =>
	{
		console.log('b')

		snap.forEach(function(entry)
		{
			const value = {
				value: '',
				validated: false
			}

			console.log('c')

			database.ref(admin, `/segments/${entry.key}/translations`).push(value)
		})
	})
})<|endoftext|>


<commit_before>/* ========================================================================
 * DBH-PG: DBH 'Class'
 * ========================================================================
 * Copyright 2014 Sapienlab
 * Licensed under MIT (https://github.com/sapienlab/dbh-pg/blob/master/LICENSE)
 * ======================================================================== */
'use strict';

var util = require('util'),
    pg = require('pg'),
    Promise = require('bluebird'),
    Connection = require('./Connection');

/**
 * @param {Object|null} setting
 * @param {Object|null} driver the pg instance
 */
function DBH(setting, driver) {
    if (!(this instanceof DBH)) {
        console.warn('need \'new\' to instantiate DBH');
        return new DBH(setting, driver);
    }
    this.driver = driver || pg;
    this.setting = setting || this.driver.defaults;
}
DBH.constructor = DBH;

DBH.prototype.conn = function(scope) {
    var dfd = Promise.defer(),
        connection = new Connection(null, scope);

    this.driver.connect(this.setting, function(err, client, done) {
        if (err) {
            done();
            dfd.reject(err);
        } else {
            connection.nativeClient = client;
            connection.done = done;
            dfd.resolve(connection);
        }
    });

    return dfd.promise.bind(connection);
};

// Define 'static' methods
[
    'exec',
    'fetchOne',
    'fetchAll',
    'fetchColumn',
    'fetchScalar',
    'insert',
    'update',
    'delete',
    'count'
].forEach(function(fnName) {
    DBH[fnName] = function() {
        var args = arguments;
        return function() {
            return this[fnName].apply(this, args);
        }
    }
});

// Other 'static' methods
[
    'begin',
    'commit',
    'rollback',
    'done'
].forEach(function(fnName) {
    DBH[fnName] = function() {
        return this[fnName].call(this);
    }
});

DBH.escape = function(sql) {
    // thanks:
    // https://github.com/felixge/node-mysql/blob/master/lib/protocol/SqlString.js#L46-L58
    // Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors
    // MIT License
    sql = sql.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
        switch(s) {
            case "\0": return "\\0";
            case "\n": return "\\n";
            case "\r": return "\\r";
            case "\b": return "\\b";
            case "\t": return "\\t";
            case "\x1a": return "\\Z";
            default: return "\\" + s;
        }
    });
    return "'" + sql + "'";
}

DBH.sqlOrderBy = function(orderBy) {
    if (!(orderBy instanceof Array)) {
        orderBy = orderBy.orderBy;
    }
    if (!orderBy || orderBy.length === 0) {
        return ' ';
    }
    var sql = ' order by ';
    sort.forEach(function(item) {
        sql += DBH.escape(item.attribute || item.attr)
            + ' '
            + (item.descending || item.desc) ? 'desc' : 'asc'
            + ', '
    });
    return sql.slice(0, -2) + ' ';
}

DBH.sqlLimit = function(limit, offset) {
    if (typeof(limit) === 'object') {
        offset = limit.offset || limit.start;
        limit = limit.limit || limit.count;
    }
    if ((offset === undefined || offset === null)
        && (limit === undefined || limit === null)) {
        return ' ';
    } else if (offset === undefined || offset === null) {
        return ' limit ' + parseInt(limit, 10) + ' ';
    } else {
        return ' limit ' + parseInt(limit, 10) +
            ' offset ' + parseInt(offset, 10) + ' ';
    }
}

DBH.prepare = function(sql) {
    var cmd = {
        name : sql.trim(),
        text : sql
    }
    return function(values) {
        if (!(values instanceof Array)) {
            values = arguments;
        }
        cmd.values = values;
        return cmd;
    }
}

module.exports = DBH;
<commit_msg>Update DBH.js<commit_after>/* ========================================================================
 * DBH-PG: DBH 'Class'
 * ========================================================================
 * Copyright 2014 Sapienlab
 * Licensed under MIT (https://github.com/sapienlab/dbh-pg/blob/master/LICENSE)
 * ======================================================================== */
'use strict';

var util = require('util'),
    pg = require('pg'),
    Promise = require('bluebird'),
    Connection = require('./Connection');

/**
 * @param {Object|null} setting
 * @param {Object|null} driver the pg instance
 */
function DBH(setting, driver) {
    if (!(this instanceof DBH)) {
        console.warn('need \'new\' to instantiate DBH');
        return new DBH(setting, driver);
    }
    this.driver = driver || pg;
    this.setting = setting || this.driver.defaults;
}
DBH.constructor = DBH;

DBH.prototype.conn = function(scope) {
    var dfd = Promise.defer(),
        connection = new Connection(null, scope);

    this.driver.connect(this.setting, function(err, client, done) {
        if (err) {
            done();
            dfd.reject(err);
        } else {
            connection.nativeClient = client;
            connection.done = done;
            dfd.resolve(connection);
        }
    });

    return dfd.promise.bind(connection);
};

// Define 'static' methods
[
    'exec',
    'fetchOne',
    'fetchAll',
    'fetchColumn',
    'fetchScalar',
    'insert',
    'update',
    'delete',
    'count'
].forEach(function(fnName) {
    DBH[fnName] = function() {
        var args = arguments;
        return function() {
            return this[fnName].apply(this, args);
        }
    }
});

// Other 'static' methods
[
    'begin',
    'commit',
    'rollback',
    'done'
].forEach(function(fnName) {
    DBH[fnName] = function() {
        return this[fnName].call(this);
    }
});

DBH.escape = function(sql) {
    // thanks:
    // https://github.com/felixge/node-mysql/blob/master/lib/protocol/SqlString.js#L46-L58
    // Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors
    // MIT License
    sql = sql.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
        switch(s) {
            case "\0": return "\\0";
            case "\n": return "\\n";
            case "\r": return "\\r";
            case "\b": return "\\b";
            case "\t": return "\\t";
            case "\x1a": return "\\Z";
            default: return "\\" + s;
        }
    });
    return "'" + sql + "'";
}

DBH.sqlOrderBy = function(orderBy) {
    if (!(orderBy instanceof Array)) {
        orderBy = orderBy.orderBy;
    }
    if (!orderBy || orderBy.length === 0) {
        return ' ';
    }
    var sql = ' order by ';
    orderBy.forEach(function(item) {
        sql += DBH.escape(item.attribute || item.attr)
            + ' '
            + (item.descending || item.desc) ? 'desc' : 'asc'
            + ', '
    });
    return sql.slice(0, -2) + ' ';
}

DBH.sqlLimit = function(limit, offset) {
    if (typeof(limit) === 'object') {
        offset = limit.offset || limit.start;
        limit = limit.limit || limit.count;
    }
    if ((offset === undefined || offset === null)
        && (limit === undefined || limit === null)) {
        return ' ';
    } else if (offset === undefined || offset === null) {
        return ' limit ' + parseInt(limit, 10) + ' ';
    } else {
        return ' limit ' + parseInt(limit, 10) +
            ' offset ' + parseInt(offset, 10) + ' ';
    }
}

DBH.prepare = function(sql) {
    var cmd = {
        name : sql.trim(),
        text : sql
    }
    return function(values) {
        if (!(values instanceof Array)) {
            values = arguments;
        }
        cmd.values = values;
        return cmd;
    }
}

module.exports = DBH;
<|endoftext|>


<commit_before>Cards = new Mongo.Collection('cards');
CardMembers = new Mongo.Collection('card_members');
CardComments = new Mongo.Collection('card_comments');

// ALLOWS
Cards.allow({
    insert: function(userId, doc) {
        return allowIsBoardMember(userId, doc.boardId);
    },
    update: function(userId, doc) {
        return allowIsBoardMember(userId, doc.boardId);
    },
    remove: function(userId, doc) {
        return allowIsBoardMember(userId, doc.boardId);
    }
});

CardMembers.allow({
    insert: function(userId, doc) {
        return allowCardMembers(userId, doc);
    },
    remove: function(userId, doc) {
        return allowIsBoardMember(userId, doc.boardId);
    }
});

// HELPERS
Cards.helpers({
    list: function() {
        return Lists.findOne(this.listId);
    },
    members: function() {
        return CardMembers.find({ cardId: this._id });
    },
    board: function() {
        return Boards.findOne(this.boardId);
    },
    user: function() {
        return Users.findOne(this.userId);
    },
    activities: function() {
        return Activities.find({ type: 'card', cardId: this._id }, { sort: { createdAt: -1 }});
    },
    comments: function() {
        return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 }});
    }
});

CardMembers.helpers({
    member: function() {
        return BoardMembers.findOne(this.memberId);
    }
});

CardComments.helpers({
    user: function() {
        return Users.findOne(this.userId);
    }
});

// CARDS BEFORE HOOK
Cards.before.insert(function(userId, doc) {
    doc.createdAt = new Date();

    // defaults
    doc.archived = false;

    // userId native set.
    if (!doc.userId) doc.userId = userId;
});

// CARDMEMBERS BEFORE HOOK
CardMembers.before.insert(function(userId, doc) {
    doc.createdAt = new Date();
});

CardComments.before.insert(function(userId, doc) {
    doc.createdAt = new Date();
    doc.userId = userId;
});

isServer(function() {
    Cards.after.insert(function(userId, doc) {
        Activities.insert({
            type: 'card',
            activityType: "createCard",
            boardId: doc.boardId,
            listId: doc.listId,
            cardId: doc._id,
            userId: userId
        });
    });

    Cards.after.update(function(userId, doc) {
        if (doc.archived) {
            Activities.insert({
                type: 'card',
                activityType: "archivedCard",
                boardId: doc.boardId,
                listId: doc.listId,
                cardId: doc._id,
                userId: userId
            });
        }
    });

    CardMembers.after.insert(function(userId, doc) {
        Activities.insert({
            type: 'card',
            activityType: "joinMember",
            memberId: doc.memberId,
            boardId: doc.boardId,
            cardId: doc.cardId,
            userId: userId
        });
    });

    CardComments.after.insert(function(userId, doc) {
        Activities.insert({
            type: 'comment',
            activityType: "addComment",
            boardId: doc.boardId,
            cardId: doc.cardId,
            commentId: doc._id,
            userId: userId
        });
    });
});
<commit_msg>card Comments collection allow<commit_after>Cards = new Mongo.Collection('cards');
CardMembers = new Mongo.Collection('card_members');
CardComments = new Mongo.Collection('card_comments');

// ALLOWS
Cards.allow({
    insert: function(userId, doc) {
        return allowIsBoardMember(userId, doc.boardId);
    },
    update: function(userId, doc) {
        return allowIsBoardMember(userId, doc.boardId);
    },
    remove: function(userId, doc) {
        return allowIsBoardMember(userId, doc.boardId);
    }
});

CardComments.allow({
    insert: function(userId, doc) {
        return allowIsBoardMember(userId, doc.boardId);
    },
    update: function(userId, doc) {
        return userId === doc.userId;
    },
    remove: function(userId, doc) {
        return userId === doc.userId;
    }
});

CardMembers.allow({
    insert: function(userId, doc) {
        return allowCardMembers(userId, doc);
    },
    remove: function(userId, doc) {
        return allowIsBoardMember(userId, doc.boardId);
    }
});

// HELPERS
Cards.helpers({
    list: function() {
        return Lists.findOne(this.listId);
    },
    members: function() {
        return CardMembers.find({ cardId: this._id });
    },
    board: function() {
        return Boards.findOne(this.boardId);
    },
    user: function() {
        return Users.findOne(this.userId);
    },
    activities: function() {
        return Activities.find({ type: 'card', cardId: this._id }, { sort: { createdAt: -1 }});
    },
    comments: function() {
        return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 }});
    }
});

CardMembers.helpers({
    member: function() {
        return BoardMembers.findOne(this.memberId);
    }
});

CardComments.helpers({
    user: function() {
        return Users.findOne(this.userId);
    }
});

// CARDS BEFORE HOOK
Cards.before.insert(function(userId, doc) {
    doc.createdAt = new Date();

    // defaults
    doc.archived = false;

    // userId native set.
    if (!doc.userId) doc.userId = userId;
});

// CARDMEMBERS BEFORE HOOK
CardMembers.before.insert(function(userId, doc) {
    doc.createdAt = new Date();
});

CardComments.before.insert(function(userId, doc) {
    doc.createdAt = new Date();
    doc.userId = userId;
});

isServer(function() {
    Cards.after.insert(function(userId, doc) {
        Activities.insert({
            type: 'card',
            activityType: "createCard",
            boardId: doc.boardId,
            listId: doc.listId,
            cardId: doc._id,
            userId: userId
        });
    });

    Cards.after.update(function(userId, doc) {
        if (doc.archived) {
            Activities.insert({
                type: 'card',
                activityType: "archivedCard",
                boardId: doc.boardId,
                listId: doc.listId,
                cardId: doc._id,
                userId: userId
            });
        }
    });

    CardMembers.after.insert(function(userId, doc) {
        Activities.insert({
            type: 'card',
            activityType: "joinMember",
            memberId: doc.memberId,
            boardId: doc.boardId,
            cardId: doc.cardId,
            userId: userId
        });
    });

    CardComments.after.insert(function(userId, doc) {
        Activities.insert({
            type: 'comment',
            activityType: "addComment",
            boardId: doc.boardId,
            cardId: doc.cardId,
            commentId: doc._id,
            userId: userId
        });
    });
});
<|endoftext|>


<commit_before>const functions = require('firebase-functions');

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
//  response.send("Hello from Firebase!");
// });
<commit_msg>add https function ifttt webhook for github data<commit_after>const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp(functions.config().firebase);
const database = admin.database();

exports.githubDataWebhook = functions.https.onRequest((req, res) => {
    if (req.method !== 'POST') {
        return res.sendStatus(404);
    }
    database.ref('data/github').once('value', (snapshot) => {
        const githubData = snapshot.val();
        const updatedData = [req.body, ...githubData.slice(0, 2)];
        snapshot.ref.set(updatedData)
            .then(() => res.sendStatus(200));
    });
});
<|endoftext|>


<commit_before>/**
 * Tab in the Bottom Navigation. Consists of Label + Icon.
 * Handles all the fancy animations.
 * @flow
 */

import React, { Component } from 'react'
import {
  View,
  Text,
  Easing,
  Animated,
  Platform,
  StyleSheet,
  TouchableWithoutFeedback
} from 'react-native'
import { easeInOut } from './utils/easing'

const useNativeDriver = Platform.OS === 'android'

type Props = {
  active: boolean,
  shifting: boolean,
  tabIndex: number,
  barBackgroundColor?: string,
  icon: ReactElement<*>,
  activeIcon?: ReactElement<*>,
  label: string,
  labelColor?: string,
  activeLabelColor?: string,
  onTabPress: () => void
}

export default class Tab extends Component {
  props: Props

  state: {
    fixed: {
      labelScale: Animated.Value,
      labelY: Animated.Value,
      iconY: Animated.Value,
      iconOpacity: Animated.Value
    },
    shifting: {
      labelOpacity: Animated.Value,
      labelScale: Animated.Value,
      iconY: Animated.Value,
      iconOpacity: Animated.Value
    }
  }

  // HACK: In shifting mode, after the first animation from active to inactive,
  //   the icon jumps down before animating to the active state again.
  //   In order to fix this, we need to store, if it already was active. Then we
  //   can catch that case and manually move it up before animating.
  //   This only happens in Android, not iOS.
  //   Is this a bug in react-native or somewhere here?
  didOnceBecameActive: boolean

  constructor(props: Props) {
    super(props)
    const { active } = props

    this.didOnceBecameActive = props.active ? true : false

    this.state = {
      fixed: {
        iconY: active ? new Animated.Value(-2) : new Animated.Value(0),
        labelScale: active ? new Animated.Value(1) : new Animated.Value(0.857),
        labelY: active ? new Animated.Value(0) : new Animated.Value(2),
        iconOpacity: active ? new Animated.Value(1) : new Animated.Value(0.8)
      },
      shifting: {
        labelOpacity: active ? new Animated.Value(1) : new Animated.Value(0),
        labelScale: active ? new Animated.Value(1) : new Animated.Value(0.857),
        iconY: active ? new Animated.Value(0) : new Animated.Value(8),
        iconOpacity: active ? new Animated.Value(1) : new Animated.Value(0.8)
      }
    }
  }

  // Animations will start as soon as new props are passed through
  componentWillReceiveProps(nextProps: Props) {
    const { props } = this

    const fixedMode = !props.shifting
    const shiftingMode = props.shifting
    const willBeActive = !props.active && nextProps.active
    const willBeInactive = props.active && !nextProps.active

    if (fixedMode && willBeActive) {
      this._animateFixedInactiveToActive()
    } else if (fixedMode && willBeInactive) {
      this._animateFixedActiveToInactive()
    } else if (shiftingMode && willBeActive) {
      this._animateShiftingInactiveToActive()
    } else if (shiftingMode && willBeInactive) {
      this._animateShiftingActiveToInactive()
    }
  }

  render() {
    const { icon, label, active } = this.props

    return (
      <TouchableWithoutFeedback onPress={this._handleTabPress}>
        <View
          style={[
            styles.container,
            (this._isShifting() && active) && styles.shiftingActiveContainer,
            (this._isShifting() && !active) && styles.shiftingInactiveContainer
          ]}
        >
          {this._renderIcon()}
          {this._renderLabel()}
        </View>
      </TouchableWithoutFeedback>
    )
  }

  _renderIcon = () => {
    const mode = this._getModeString()
    const { active, icon, activeIcon } = this.props

    return (
      <Animated.View
        style={[
          styles.icon,
          { transform: [{ translateY: this.state[mode].iconY }] },
          { opacity: this.state[mode].iconOpacity }
        ]}
      >
        <View ref="_bnic" collapsable={false}>
          {active && activeIcon ? activeIcon : icon}
        </View>
      </Animated.View>
    )
  }

  _renderLabel = () => {
    const { active, labelColor, activeLabelColor, label } = this.props

    return (
      <Animated.Text
        style={[
          { color: active && activeLabelColor ? activeLabelColor : labelColor },
          styles.label,
          this._isShifting() && { opacity: this.state.shifting.labelOpacity },
          this._isFixed() && {
            transform: [
              { scale: this.state.fixed.labelScale },
              { translateY: this.state.fixed.labelY }
            ]
          }
        ]}
        numberOfLines={1}
      >
        {label}
      </Animated.Text>
    )
  }

  _animateFixedInactiveToActive = () => {
    const duration = 266
    const easing = easeInOut

    Animated.parallel([
      Animated.timing(this.state.fixed.iconY,
        { toValue: -2, duration, easing, useNativeDriver }),
      Animated.timing(this.state.fixed.labelScale,
        { toValue: 1, duration, easing, useNativeDriver }),
      Animated.timing(this.state.fixed.labelY,
        { toValue: 0, duration, easing, useNativeDriver }),
      Animated.timing(this.state.fixed.iconOpacity,
        { toValue: 1, duration, easing, useNativeDriver })
    ]).start()
  }

  _animateFixedActiveToInactive = () => {
    const duration = 266
    const easing = easeInOut

    Animated.parallel([
      Animated.timing(this.state.fixed.iconY,
        { toValue: 0, duration, easing, useNativeDriver }),
      Animated.timing(this.state.fixed.labelScale,
        { toValue: 0.857, duration, easing, useNativeDriver }),
      Animated.timing(this.state.fixed.labelY,
        { toValue: 2, duration, easing, useNativeDriver }),
      Animated.timing(this.state.fixed.iconOpacity,
        { toValue: 0.8, duration, easing, useNativeDriver })
    ]).start()
  }

  _animateShiftingInactiveToActive = () => {
    const easing = easeInOut

    // HACK: See above "didOnceBecameActive"
    if (Platform.OS === 'android') {
      if (this.didOnceBecameActive) this.state.shifting.iconY.setValue(0)
      this.didOnceBecameActive = true
    }

    Animated.parallel([
      Animated.timing(this.state.shifting.iconY,
        { toValue: 0, duration: 266, easing, useNativeDriver }),
      Animated.timing(this.state.shifting.iconOpacity,
        { toValue: 1, duration: 266, easing, useNativeDriver }),
      Animated.timing(this.state.shifting.labelOpacity,
        { toValue: 1, duration: 183, delay: 83, easing, useNativeDriver }),
      Animated.timing(this.state.shifting.labelScale,
        { toValue: 1, duration: 183, delay: 83, easing, useNativeDriver })
    ]).start()
  }

  _animateShiftingActiveToInactive = () => {
    const easing = easeInOut

    Animated.parallel([
      Animated.timing(this.state.shifting.iconY,
        { toValue: 8, duration: 266, easing, useNativeDriver }),
      Animated.timing(this.state.shifting.labelOpacity,
        { toValue: 0, duration: 83, easing, useNativeDriver }),
      Animated.timing(this.state.shifting.labelScale,
        { toValue: 0.857, duration: 83, easing, useNativeDriver }),
      Animated.timing(this.state.shifting.iconOpacity,
        { toValue: 0.8, duration: 266, easing, useNativeDriver })
    ]).start()
  }

  _handleTabPress = () => {
    this.setTabActive()
  }

  setTabActive = () => {
    // setting the tab active is job of the BottomNavigation Component,
    // so call it's function to handle that.
    this.props.onTabPress({
      tabIndex: this.props.tabIndex,
      barBackgroundColor: this.props.barBackgroundColor,
      iconRef: this.refs._bnic
    })
  }

  _getModeString = () => {
    if (this.props.shifting) return 'shifting'
    return 'fixed'
  }

  _isShifting = () => {
    return !!this.props.shifting
  }

  _isFixed = () => {
    return !this.props.shifting
  }

  getIconRef = () => {
    return this.refs._bnic
  }
}

const styles = StyleSheet.create({
  container: {
    height: 56,
    flex: 1,
    alignItems: 'center',
    paddingTop: 8,
    paddingBottom: 10,
    paddingLeft: 12,
    paddingRight: 12,
    backgroundColor: 'transparent'
  },
  shiftingInactiveContainer: {
    maxWidth: 96,
    minWidth: 56,
    flex: 1
  },
  shiftingActiveContainer: {
    maxWidth: 168,
    minWidth: 96,
    flex: 1.75
  },
  icon: {
    width: 24,
    height: 24,
    backgroundColor: 'transparent'
  },
  label: {
    fontSize: 14,
    width: 168,
    textAlign: 'center',
    includeFontPadding: false,
    textAlignVertical: 'center',
    justifyContent: 'flex-end',
    backgroundColor: 'transparent'
  }
})
<commit_msg>Code cleaning Tab<commit_after>/**
 * Tab in the Bottom Navigation. Consists of Label + Icon.
 * Handles all the fancy animations.
 * @flow
 */

import React, { Component } from 'react'
import {
  View,
  Text,
  Easing,
  Animated,
  Platform,
  StyleSheet,
  TouchableWithoutFeedback
} from 'react-native'
import { easeInOut } from './utils/easing'

const useNativeDriver = Platform.OS === 'android'

type TabProps = {
  active: boolean,
  shifting: boolean,
  tabIndex: number,
  barBackgroundColor: string,
  icon: ReactElement<*>,
  activeIcon?: ReactElement<*>,
  label: string,
  labelColor: string,
  activeLabelColor?: string,
  onTabPress: () => void
}

type TabState = {
  fixed: {
    labelScale: Animated.Value,
    labelY: Animated.Value,
    iconY: Animated.Value,
    iconOpacity: Animated.Value
  },
  shifting: {
    labelOpacity: Animated.Value,
    labelScale: Animated.Value,
    iconY: Animated.Value,
    iconOpacity: Animated.Value
  }
}

export default class Tab extends Component {

  props: TabProps
  state: TabState
  didOnceBecameActive: boolean

  constructor(props: TabProps) {
    super(props)
    const { active } = props

    // HACK: In shifting mode, after the first animation from active to
    //   inactive, the icon jumps down before animating to the active state
    //   again. In order to fix this, we need to store, if it already was
    //   active. Then we can catch that case and manually move it up before
    //   animating. This only happens in Android, not iOS.
    //   Is this a bug in react-native or somewhere here?
    this.didOnceBecameActive = props.active ? true : false

    this.state = {
      fixed: {
        iconY: active ? new Animated.Value(-2) : new Animated.Value(0),
        labelScale: active ? new Animated.Value(1) : new Animated.Value(0.857),
        labelY: active ? new Animated.Value(0) : new Animated.Value(2),
        iconOpacity: active ? new Animated.Value(1) : new Animated.Value(0.8)
      },
      shifting: {
        labelOpacity: active ? new Animated.Value(1) : new Animated.Value(0),
        labelScale: active ? new Animated.Value(1) : new Animated.Value(0.857),
        iconY: active ? new Animated.Value(0) : new Animated.Value(8),
        iconOpacity: active ? new Animated.Value(1) : new Animated.Value(0.8)
      }
    }
  }

  // Animations will start as soon as new props are passed through
  componentWillReceiveProps(nextProps: TabProps) {
    const { props } = this

    const fixedMode = !props.shifting
    const shiftingMode = props.shifting
    const willBeActive = !props.active && nextProps.active
    const willBeInactive = props.active && !nextProps.active

    if (fixedMode && willBeActive) {
      this._animateFixedInactiveToActive()
    } else if (fixedMode && willBeInactive) {
      this._animateFixedActiveToInactive()
    } else if (shiftingMode && willBeActive) {
      this._animateShiftingInactiveToActive()
    } else if (shiftingMode && willBeInactive) {
      this._animateShiftingActiveToInactive()
    }
  }

  render() {
    const { icon, label, active } = this.props

    return (
      <TouchableWithoutFeedback onPress={this._handleTabPress}>
        <View
          style={[
            styles.container,
            (this._isShifting() && active) && styles.shiftingActiveContainer,
            (this._isShifting() && !active) && styles.shiftingInactiveContainer
          ]}
        >
          {this._renderIcon()}
          {this._renderLabel()}
        </View>
      </TouchableWithoutFeedback>
    )
  }

  _renderIcon = () => {
    const mode = this._getModeString()
    const { active, icon, activeIcon } = this.props

    return (
      <Animated.View
        style={[
          styles.icon,
          { transform: [{ translateY: this.state[mode].iconY }] },
          { opacity: this.state[mode].iconOpacity }
        ]}
      >
        <View ref="_bnic" collapsable={false}>
          {active && activeIcon ? activeIcon : icon}
        </View>
      </Animated.View>
    )
  }

  _renderLabel = () => {
    const { active, labelColor, activeLabelColor, label } = this.props

    return (
      <Animated.Text
        style={[
          { color: active && activeLabelColor ? activeLabelColor : labelColor },
          styles.label,
          this._isShifting() && { opacity: this.state.shifting.labelOpacity },
          this._isFixed() && {
            transform: [
              { scale: this.state.fixed.labelScale },
              { translateY: this.state.fixed.labelY }
            ]
          }
        ]}
        numberOfLines={1}
      >
        {label}
      </Animated.Text>
    )
  }

  _animateFixedInactiveToActive = () => {
    const duration = 266
    const easing = easeInOut

    Animated.parallel([
      Animated.timing(this.state.fixed.iconY,
        { toValue: -2, duration, easing, useNativeDriver }),
      Animated.timing(this.state.fixed.labelScale,
        { toValue: 1, duration, easing, useNativeDriver }),
      Animated.timing(this.state.fixed.labelY,
        { toValue: 0, duration, easing, useNativeDriver }),
      Animated.timing(this.state.fixed.iconOpacity,
        { toValue: 1, duration, easing, useNativeDriver })
    ]).start()
  }

  _animateFixedActiveToInactive = () => {
    const duration = 266
    const easing = easeInOut

    Animated.parallel([
      Animated.timing(this.state.fixed.iconY,
        { toValue: 0, duration, easing, useNativeDriver }),
      Animated.timing(this.state.fixed.labelScale,
        { toValue: 0.857, duration, easing, useNativeDriver }),
      Animated.timing(this.state.fixed.labelY,
        { toValue: 2, duration, easing, useNativeDriver }),
      Animated.timing(this.state.fixed.iconOpacity,
        { toValue: 0.8, duration, easing, useNativeDriver })
    ]).start()
  }

  _animateShiftingInactiveToActive = () => {
    const easing = easeInOut

    // HACK: See above "didOnceBecameActive"
    if (Platform.OS === 'android') {
      if (this.didOnceBecameActive) this.state.shifting.iconY.setValue(0)
      this.didOnceBecameActive = true
    }

    Animated.parallel([
      Animated.timing(this.state.shifting.iconY,
        { toValue: 0, duration: 266, easing, useNativeDriver }),
      Animated.timing(this.state.shifting.iconOpacity,
        { toValue: 1, duration: 266, easing, useNativeDriver }),
      Animated.timing(this.state.shifting.labelOpacity,
        { toValue: 1, duration: 183, delay: 83, easing, useNativeDriver }),
      Animated.timing(this.state.shifting.labelScale,
        { toValue: 1, duration: 183, delay: 83, easing, useNativeDriver })
    ]).start()
  }

  _animateShiftingActiveToInactive = () => {
    const easing = easeInOut

    Animated.parallel([
      Animated.timing(this.state.shifting.iconY,
        { toValue: 8, duration: 266, easing, useNativeDriver }),
      Animated.timing(this.state.shifting.labelOpacity,
        { toValue: 0, duration: 83, easing, useNativeDriver }),
      Animated.timing(this.state.shifting.labelScale,
        { toValue: 0.857, duration: 83, easing, useNativeDriver }),
      Animated.timing(this.state.shifting.iconOpacity,
        { toValue: 0.8, duration: 266, easing, useNativeDriver })
    ]).start()
  }

  _handleTabPress = () => {
    this.setTabActive()
  }

  setTabActive = () => {
    // Setting the tab active is job of the BottomNavigation Component,
    // so call it's function to handle that.
    this.props.onTabPress({
      tabIndex: this.props.tabIndex,
      barBackgroundColor: this.props.barBackgroundColor,
      iconRef: this.refs._bnic
    })
  }

  _getModeString = () => {
    if (this.props.shifting) return 'shifting'
    return 'fixed'
  }

  _isShifting = () => {
    return !!this.props.shifting
  }

  _isFixed = () => {
    return !this.props.shifting
  }

  getIconRef = () => {
    return this.refs._bnic
  }
}

const styles = StyleSheet.create({
  container: {
    height: 56,
    flex: 1,
    alignItems: 'center',
    paddingTop: 8,
    paddingBottom: 10,
    paddingLeft: 12,
    paddingRight: 12,
    backgroundColor: 'transparent'
  },
  shiftingInactiveContainer: {
    maxWidth: 96,
    minWidth: 56,
    flex: 1
  },
  shiftingActiveContainer: {
    maxWidth: 168,
    minWidth: 96,
    flex: 1.75
  },
  icon: {
    width: 24,
    height: 24,
    backgroundColor: 'transparent'
  },
  label: {
    fontSize: 14,
    width: 168,
    textAlign: 'center',
    includeFontPadding: false,
    textAlignVertical: 'center',
    justifyContent: 'flex-end',
    backgroundColor: 'transparent'
  }
})
<|endoftext|>