<commit_before>/* eslint-disable no-param-reassign,prefer-promise-reject-errors */
import Promise from 'bluebird';

/*
  A general purpose user CordovaPush
  ios, android, mail, twitter?, facebook?, sms?, snailMail? :)

  Phonegap generic :
  https://github.com/phonegap-build/PushPlugin
 */

// getText / getBinary

Push.setBadge = function (/* id, count */) {
  // throw new Error('Push.setBadge not implemented on the server');
};

Push.FirebaseApps = {};

let isConfigured = false;

const defaults = {
  sendInterval: 5000
};

Push.Configure = function (options) {
  const self = this;

  let cfgOptions = _.extend(defaults, options);
  // https://npmjs.org/package/apn

  // After requesting the certificate from Apple, export your private key as
  // a .p12 file anddownload the .cer file from the iOS Provisioning Portal.

  // gateway.push.apple.com, port 2195
  // gateway.sandbox.push.apple.com, port 2195

  // Now, in the directory containing cert.cer and key.p12 execute the
  // following commands to generate your .pem files:
  // $ openssl x509 -in cert.cer -inform DER -outform PEM -out cert.pem
  // $ openssl pkcs12 -in key.p12 -out key.pem -nodes

  // Block multiple calls
  if (isConfigured) {
    console.warn('Push.Configure should only be called once.');
  }

  isConfigured = true;

  // Add debug info
  if (Push.debug) {
    console.log('Push.Configure', options);
  }

  // This function is called when a token is replaced on a device - normally
  // this should not happen, but if it does we should take action on it
  _replaceToken = function (currentToken, newToken) {
    // console.log('Replace token: ' + currentToken + ' -- ' + newToken);
    // If the server gets a token event its passing in the current token and
    // the new value - if new value is undefined this empty the token
    self.emitState('token', currentToken, newToken);
  };

  // Rig the removeToken callback
  _removeToken = function (token) {
    console.log('Remove token: ' + token);
    // Invalidate the token
    self.emitState('token', token, null);
  };

  if (Push.debug) {
    console.log('Push: APN configured');
  }

  const useProductionCert = process.env.NODE_ENV === 'production';

  if (!useProductionCert) {
    console.log('This is being configured in development mode');
  }

  const vets = GlobalVets.find({$or: [{disabled: {$exists: false}}, {disabled: false}]}).fetch();
  vets.forEach((vet) => {
    let daysRemaining = Meteor.call('checkCertificate', vet._id);

    if (daysRemaining > 1) {
      Meteor.call('raix:configure-push-connections', vet._id);
    }
  });

  // This interval will allow only one notification to be sent at a time, it
  // will check for new notifications at every `options.sendInterval`
  // (default interval is 15000 ms)
  //
  // It looks in notifications collection to see if theres any pending
  // notifications, if so it will try to reserve the pending notification.
  // If successfully reserved the send is started.
  //
  // If notification.query is type string, it's assumed to be a json string
  // version of the query selector. Making it able to carry `$` properties in
  // the mongo collection.
  //
  // Pr. default notifications are removed from the collection after send have
  // completed. Setting `options.keepNotifications` will update and keep the
  // notification eg. if needed for historical reasons.
  //
  // After the send have completed a "send" event will be emitted with a
  // status object containing notification id and the send result object.
  //
  let isSendingNotification = false;

  if (cfgOptions.sendInterval !== null && !(process.env.PUSH_NOTIFICATIONS_DISABLED === true || process.env.PUSH_NOTIFICATIONS_DISABLED === 'true')) {
    // This will require index since we sort notifications by createdAt
    Push.notifications._ensureIndex({createdAt: 1});

    Meteor.setInterval(function () {
      if (isSendingNotification) {
        return;
      }
      // Set send fence
      isSendingNotification = true;

      // var countSent = 0;
      const batchSize = cfgOptions.sendBatchSize || 100;

      // Find notifications that are not being or already sent
      const pendingNotifications = Push.notifications.find({
        $and: [
          // Message is not sent
          {sent: {$ne: true}},
          // And not being sent by other instances
          {sending: {$ne: true}},
          // And not queued for future
          {$or: [{delayUntil: {$exists: false}}, {delayUntil: {$lte: new Date()}}]}
        ]
      }, {
        // Sort by created date
        sort: {createdAt: 1},
        limit: batchSize
      }).fetch();

      pendingNotifications.forEach(function (notification) {
        console.log('Found notification, reserving...');

        if (process.env.NODE_ENV === 'development') {
          console.log('DEV environment - not sending the notification', notification);
          return;
        }

        // Reserve notification
        const reserved = Push.notifications.update({
          $and: [
            // Try to reserve the current notification
            {_id: notification._id},
            // Make sure no other instances have reserved it
            {sending: {$ne: true}}
          ]
        }, {
          $set: {
            // Try to reserve
            sending: true
          }
        });

        // Make sure we only handle notifications reserved by this
        // instance
        if (reserved) {
          console.log('Notification reserved, processing...');

          // Check if query is set and is type String
          if (notification.query && notification.query === '' + notification.query) {
            try {
              // The query is in string json format - we need to parse it
              notification.query = JSON.parse(notification.query);
            } catch (err) {
              // Did the user tamper with this??
              throw new Error('Push: Error while parsing query string, Error: ' + err.message);
            }
          }

          // Send the notification
          serverSend(notification)
            .then(result => {
              // eslint-disable-next-line arrow-body-style
              if (result.some(item => item.isFulfilled())) {
                result.forEach(function (item) {
                  if (!item.isFulfilled()) {
                    handleFailedNotification(notification, item.value());
                  }
                });

                if (!cfgOptions.keepNotifications) {
                  // Pr. Default we will remove notifications
                  Push.notifications.remove({_id: notification._id});
                } else {

                  // Update the notification
                  Push.notifications.update({_id: notification._id}, {
                    $set: {
                      // Mark as sent
                      sent: true,
                      // Set the sent date
                      sentAt: new Date(),
                      // Not being sent anymore
                      sending: false
                    }
                  });

                }
              } else {
                resendEntireNotification(notification);
              }
            })
            .catch(e => {
              console.log('Error sending notification');
              if (e instanceof Error) {
                console.log(e.toString());
              } else {
                console.log(JSON.stringify(e));
              }

              resendEntireNotification(notification);

            });

          // Emit the send
          self.emit('send', {notification: notification._id, result: result});

        } // Else could not reserve

      }); // EO forEach

      // Remove the send fence
      isSendingNotification = false;
    }, cfgOptions.sendInterval || 5000); // Default every 15th sec
  } else if (Push.debug) {
    console.log('Push: Send server is disabled');
  }
};

function serverSend(raixNotification) {
  raixNotification = raixNotification || {};
  let query;

  // Check basic raixNotification
  if (typeof raixNotification.from !== 'string') {
    throw new Error('Push.send: option "from" not a string');
  }

  if (typeof raixNotification.title !== 'string') {
    throw new Error('Push.send: option "title" not a string');
  }

  if (typeof raixNotification.text !== 'string') {
    throw new Error('Push.send: option "text" not a string');
  }

  if (raixNotification.token || raixNotification.tokens) {
    // The user set one token or array of tokens
    const tokenList = (raixNotification.token) ? [raixNotification.token] : raixNotification.tokens;

    if (Push.debug) {
      console.log('Push: Send message "' + raixNotification.title + '" via token(s)', tokenList);
    }

    query = {
      $or: [
        // XXX: Test this query: can we hand in a list of push tokens?
        {
          $and: [
            {appName: raixNotification.appIdentifier},
            {token: {$in: tokenList}},
            // And is not disabled
            {enabled: {$ne: false}}
          ]
        },
        // XXX: Test this query: does this work on app id?
        {
          $and: [
            {appName: raixNotification.appIdentifier},
            {_in: {$in: tokenList}}, // one of the app ids
            {'token.fcm': {$exists: true}},
            // And is not disabled
            {enabled: {$ne: false}}
          ]
        }
      ]
    };

  } else if (raixNotification.query) {
    if (Push.debug) {
      console.log('Push: Send message "' + raixNotification.title + '" via query', raixNotification.query);
    }

    query = {
      $and: [
        raixNotification.query, // query object
        {appName: raixNotification.appIdentifier},
        {'token.fcm': {$exists: true}},
        {enabled: {$ne: false}} // And is not disabled
      ]
    };
  }

  if (query) {
    // Convert to querySend and return status
    return querySend(query, raixNotification);
  } else if (raixNotification.topics) {
    return topicsSend(raixNotification.topics, raixNotification);
  } else {
    throw new Error('Push.send: please set option "token"/"tokens" or "query"');
  }
}

function querySend(query, raixNotification) {
  console.log('Sending notifications for query: ', query);

  let firebasePromises = [];

  Push.appTokens.find(query).forEach(function (appToken) {
    console.log('Iterating...', appToken);

    if (Push.debug) {
      console.log('send to token', appToken.token);
    }

    if (appToken.token.fcm) {
      console.log('appToken.token.fcm', 'passed');
      firebasePromises.push(new Promise((resolve, reject) => {
        fcmSingleMessage(appToken.token.fcm, raixNotification)
          .then(result => {
            resolve(_.extend(result, {tokenId: appToken._id, platform: appToken.platform}));
          })
          .catch(error => {
            reject(error);
          });
      }));
    } else {
      firebasePromises.push(Promise.reject({code: 'push/no-fcm-token', message: `ERROR: PUSH.querySend - No fcm token:  ${JSON.stringify(appToken)}`}));
    }

  });

  let allMessagesPromise = Promise.all(firebasePromises.map(function (promise) {
    return promise.reflect();
  }));

  if (Push.debug) {
    allMessagesPromise.then(result => {
      let androidCnt = 0;
      let iosCnt = 0;
      let unknownCnt = 0;
      let failedCnt = 0;

      result.forEach(function (item) {
        if (item.isFulfilled()) {
          if (item.value().platform) {
            androidCnt += /android/i.test(item.value().platform) ? 1 : 0;
            iosCnt += /ios/i.test(item.value().platform) ? 1 : 0;
            unknownCnt += (!item.value().platform) ? 1 : 0;
          }
        } else {
          failedCnt++;
        }
      });

      console.log(`Push: Sent message "${raixNotification.title}" to ${iosCnt} ios apps, ${androidCnt} android apps and ${unknownCnt} unknown apps. Failed to send ${failedCnt} messages`);

    });
  }

  return allMessagesPromise;

}

function topicsSend(topics, raixNotification) {
  let firebasePromises = [];

  topics.forEach(function (topic) {
    const fcmMessage = getFcmMessageForTopic(topic, raixNotification);
    const appIdentifier = raixNotification.appIdentifier;
    firebasePromises.push(sendFcmMessage(appIdentifier, fcmMessage));
  });

  return Promise.all(firebasePromises.map(function (promise) {
    return promise.reflect();
  }));
}

function fcmSingleMessage(userFcmToken, raixNotification) {
  // https://firebase.google.com/docs/reference/admin/node/admin.messaging.Message
  let fcmMessage = getFcmMessageForIndividualDevice(userFcmToken, raixNotification);
  const appIdentifier = raixNotification.appIdentifier;
  return sendFcmMessage(appIdentifier, fcmMessage);
}

function sendFcmMessage(appIdentifier, fcmMessage) {
  if (!Match.test(appIdentifier, String)) {
    console.error('ERROR: PUSH.sendFCM - No appIdentifier found for notification: ', fcmMessage);
    return Promise.reject({
      code: 'push/no-app-identifier',
      message: `ERROR: PUSH.sendFCM - No appIdentifier found for notification:  ${JSON.stringify(fcmMessage)}`,
      fcmMessage: fcmMessage
    });
  }
  const firebaseApp = Push.FirebaseApps[appIdentifier];
  if (!firebaseApp) {
    console.error('ERROR: PUSH.sendFCM - No firebase app found for notification: ', fcmMessage);
    return Promise.reject({
      code: 'push/no-firebase-app-found',
      message: `ERROR: PUSH.sendFCM - No firebase app found for notification:  ${JSON.stringify(fcmMessage)}`,
      fcmMessage: fcmMessage
    });
  }

  return new Promise((resolve, reject) => {
    firebaseApp.messaging().send(fcmMessage)
      .then(result => {
        resolve(result);
      })
      .catch(reason => {
        reject(_.extend(reason, {fcmMessage: fcmMessage}));
      });
  });
}

function getFcmMessageForIndividualDevice(userFcmToken, raixNotification) {
  let fcmMessage = getFcmMessage(raixNotification);
  fcmMessage.token = userFcmToken;
  return fcmMessage;
}

function getFcmMessageForTopic(topicName, raixNotification) {
  let fcmMessage = getFcmMessage(raixNotification);
  fcmMessage.topic = topicName;
  return fcmMessage;
}

function getFcmMessage(raixNotification) {
  return {
    notification: {
      title: raixNotification.title,
      body: raixNotification.text
    },
    data: raixNotification.payload,
    android: {
      notification: {
        sound: raixNotification.gcm.sound
      }
    },
    apns: {
      headers: {
        'apns-expiration': moment.utc().add(30, 'days').unix()
      },
      payload: {
        aps: {
          sound: raixNotification.apn.sound,
          badge: raixNotification.badge
        }
      }
    }
  };
}

function resendEntireNotification(notification) {
  if (!notification.failedCount || notification.failedCount <= 5) {
    let failedCount = (notification.failedCount) ? notification.failedCount + 1 : 1;

    Push.notifications.update({_id: notification._id}, {
      $set: {
        sent: false,
        sending: false,
        failedCount: failedCount,
        delayUntil: moment().add(2 ** failedCount, 'minutes').toDate()
      }
    });
  }
}

function handleFailedNotification(notification, reason) {
  // TODO @mj implement error handling and conditions
  resendFailedNotification(notification, reason.fcmMessage);
}

function resendFailedNotification(notification, fcmMessage) {
  // TODO @mj implement resend
}
<commit_msg>Fix es2016 syntax.<commit_after>/* eslint-disable no-param-reassign,prefer-promise-reject-errors */
import Promise from 'bluebird';

/*
  A general purpose user CordovaPush
  ios, android, mail, twitter?, facebook?, sms?, snailMail? :)

  Phonegap generic :
  https://github.com/phonegap-build/PushPlugin
 */

// getText / getBinary

Push.setBadge = function (/* id, count */) {
  // throw new Error('Push.setBadge not implemented on the server');
};

Push.FirebaseApps = {};

let isConfigured = false;

const defaults = {
  sendInterval: 5000
};

Push.Configure = function (options) {
  const self = this;

  let cfgOptions = _.extend(defaults, options);
  // https://npmjs.org/package/apn

  // After requesting the certificate from Apple, export your private key as
  // a .p12 file anddownload the .cer file from the iOS Provisioning Portal.

  // gateway.push.apple.com, port 2195
  // gateway.sandbox.push.apple.com, port 2195

  // Now, in the directory containing cert.cer and key.p12 execute the
  // following commands to generate your .pem files:
  // $ openssl x509 -in cert.cer -inform DER -outform PEM -out cert.pem
  // $ openssl pkcs12 -in key.p12 -out key.pem -nodes

  // Block multiple calls
  if (isConfigured) {
    console.warn('Push.Configure should only be called once.');
  }

  isConfigured = true;

  // Add debug info
  if (Push.debug) {
    console.log('Push.Configure', options);
  }

  // This function is called when a token is replaced on a device - normally
  // this should not happen, but if it does we should take action on it
  _replaceToken = function (currentToken, newToken) {
    // console.log('Replace token: ' + currentToken + ' -- ' + newToken);
    // If the server gets a token event its passing in the current token and
    // the new value - if new value is undefined this empty the token
    self.emitState('token', currentToken, newToken);
  };

  // Rig the removeToken callback
  _removeToken = function (token) {
    console.log('Remove token: ' + token);
    // Invalidate the token
    self.emitState('token', token, null);
  };

  if (Push.debug) {
    console.log('Push: APN configured');
  }

  const useProductionCert = process.env.NODE_ENV === 'production';

  if (!useProductionCert) {
    console.log('This is being configured in development mode');
  }

  const vets = GlobalVets.find({$or: [{disabled: {$exists: false}}, {disabled: false}]}).fetch();
  vets.forEach((vet) => {
    let daysRemaining = Meteor.call('checkCertificate', vet._id);

    if (daysRemaining > 1) {
      Meteor.call('raix:configure-push-connections', vet._id);
    }
  });

  // This interval will allow only one notification to be sent at a time, it
  // will check for new notifications at every `options.sendInterval`
  // (default interval is 15000 ms)
  //
  // It looks in notifications collection to see if theres any pending
  // notifications, if so it will try to reserve the pending notification.
  // If successfully reserved the send is started.
  //
  // If notification.query is type string, it's assumed to be a json string
  // version of the query selector. Making it able to carry `$` properties in
  // the mongo collection.
  //
  // Pr. default notifications are removed from the collection after send have
  // completed. Setting `options.keepNotifications` will update and keep the
  // notification eg. if needed for historical reasons.
  //
  // After the send have completed a "send" event will be emitted with a
  // status object containing notification id and the send result object.
  //
  let isSendingNotification = false;

  if (cfgOptions.sendInterval !== null && !(process.env.PUSH_NOTIFICATIONS_DISABLED === true || process.env.PUSH_NOTIFICATIONS_DISABLED === 'true')) {
    // This will require index since we sort notifications by createdAt
    Push.notifications._ensureIndex({createdAt: 1});

    Meteor.setInterval(function () {
      if (isSendingNotification) {
        return;
      }
      // Set send fence
      isSendingNotification = true;

      // var countSent = 0;
      const batchSize = cfgOptions.sendBatchSize || 100;

      // Find notifications that are not being or already sent
      const pendingNotifications = Push.notifications.find({
        $and: [
          // Message is not sent
          {sent: {$ne: true}},
          // And not being sent by other instances
          {sending: {$ne: true}},
          // And not queued for future
          {$or: [{delayUntil: {$exists: false}}, {delayUntil: {$lte: new Date()}}]}
        ]
      }, {
        // Sort by created date
        sort: {createdAt: 1},
        limit: batchSize
      }).fetch();

      pendingNotifications.forEach(function (notification) {
        console.log('Found notification, reserving...');

        if (process.env.NODE_ENV === 'development') {
          console.log('DEV environment - not sending the notification', notification);
          return;
        }

        // Reserve notification
        const reserved = Push.notifications.update({
          $and: [
            // Try to reserve the current notification
            {_id: notification._id},
            // Make sure no other instances have reserved it
            {sending: {$ne: true}}
          ]
        }, {
          $set: {
            // Try to reserve
            sending: true
          }
        });

        // Make sure we only handle notifications reserved by this
        // instance
        if (reserved) {
          console.log('Notification reserved, processing...');

          // Check if query is set and is type String
          if (notification.query && notification.query === '' + notification.query) {
            try {
              // The query is in string json format - we need to parse it
              notification.query = JSON.parse(notification.query);
            } catch (err) {
              // Did the user tamper with this??
              throw new Error('Push: Error while parsing query string, Error: ' + err.message);
            }
          }

          // Send the notification
          serverSend(notification)
            .then(result => {
              // eslint-disable-next-line arrow-body-style
              if (result.some(item => item.isFulfilled())) {
                result.forEach(function (item) {
                  if (!item.isFulfilled()) {
                    handleFailedNotification(notification, item.value());
                  }
                });

                if (!cfgOptions.keepNotifications) {
                  // Pr. Default we will remove notifications
                  Push.notifications.remove({_id: notification._id});
                } else {

                  // Update the notification
                  Push.notifications.update({_id: notification._id}, {
                    $set: {
                      // Mark as sent
                      sent: true,
                      // Set the sent date
                      sentAt: new Date(),
                      // Not being sent anymore
                      sending: false
                    }
                  });

                }
              } else {
                resendEntireNotification(notification);
              }
            })
            .catch(e => {
              console.log('Error sending notification');
              if (e instanceof Error) {
                console.log(e.toString());
              } else {
                console.log(JSON.stringify(e));
              }

              resendEntireNotification(notification);

            });

          // Emit the send
          self.emit('send', {notification: notification._id, result: result});

        } // Else could not reserve

      }); // EO forEach

      // Remove the send fence
      isSendingNotification = false;
    }, cfgOptions.sendInterval || 5000); // Default every 15th sec
  } else if (Push.debug) {
    console.log('Push: Send server is disabled');
  }
};

function serverSend(raixNotification) {
  raixNotification = raixNotification || {};
  let query;

  // Check basic raixNotification
  if (typeof raixNotification.from !== 'string') {
    throw new Error('Push.send: option "from" not a string');
  }

  if (typeof raixNotification.title !== 'string') {
    throw new Error('Push.send: option "title" not a string');
  }

  if (typeof raixNotification.text !== 'string') {
    throw new Error('Push.send: option "text" not a string');
  }

  if (raixNotification.token || raixNotification.tokens) {
    // The user set one token or array of tokens
    const tokenList = (raixNotification.token) ? [raixNotification.token] : raixNotification.tokens;

    if (Push.debug) {
      console.log('Push: Send message "' + raixNotification.title + '" via token(s)', tokenList);
    }

    query = {
      $or: [
        // XXX: Test this query: can we hand in a list of push tokens?
        {
          $and: [
            {appName: raixNotification.appIdentifier},
            {token: {$in: tokenList}},
            // And is not disabled
            {enabled: {$ne: false}}
          ]
        },
        // XXX: Test this query: does this work on app id?
        {
          $and: [
            {appName: raixNotification.appIdentifier},
            {_in: {$in: tokenList}}, // one of the app ids
            {'token.fcm': {$exists: true}},
            // And is not disabled
            {enabled: {$ne: false}}
          ]
        }
      ]
    };

  } else if (raixNotification.query) {
    if (Push.debug) {
      console.log('Push: Send message "' + raixNotification.title + '" via query', raixNotification.query);
    }

    query = {
      $and: [
        raixNotification.query, // query object
        {appName: raixNotification.appIdentifier},
        {'token.fcm': {$exists: true}},
        {enabled: {$ne: false}} // And is not disabled
      ]
    };
  }

  if (query) {
    // Convert to querySend and return status
    return querySend(query, raixNotification);
  } else if (raixNotification.topics) {
    return topicsSend(raixNotification.topics, raixNotification);
  } else {
    throw new Error('Push.send: please set option "token"/"tokens" or "query"');
  }
}

function querySend(query, raixNotification) {
  console.log('Sending notifications for query: ', query);

  let firebasePromises = [];

  Push.appTokens.find(query).forEach(function (appToken) {
    console.log('Iterating...', appToken);

    if (Push.debug) {
      console.log('send to token', appToken.token);
    }

    if (appToken.token.fcm) {
      console.log('appToken.token.fcm', 'passed');
      firebasePromises.push(new Promise((resolve, reject) => {
        fcmSingleMessage(appToken.token.fcm, raixNotification)
          .then(result => {
            resolve(_.extend(result, {tokenId: appToken._id, platform: appToken.platform}));
          })
          .catch(error => {
            reject(error);
          });
      }));
    } else {
      firebasePromises.push(Promise.reject({code: 'push/no-fcm-token', message: `ERROR: PUSH.querySend - No fcm token:  ${JSON.stringify(appToken)}`}));
    }

  });

  let allMessagesPromise = Promise.all(firebasePromises.map(function (promise) {
    return promise.reflect();
  }));

  if (Push.debug) {
    allMessagesPromise.then(result => {
      let androidCnt = 0;
      let iosCnt = 0;
      let unknownCnt = 0;
      let failedCnt = 0;

      result.forEach(function (item) {
        if (item.isFulfilled()) {
          if (item.value().platform) {
            androidCnt += /android/i.test(item.value().platform) ? 1 : 0;
            iosCnt += /ios/i.test(item.value().platform) ? 1 : 0;
            unknownCnt += (!item.value().platform) ? 1 : 0;
          }
        } else {
          failedCnt++;
        }
      });

      console.log(`Push: Sent message "${raixNotification.title}" to ${iosCnt} ios apps, ${androidCnt} android apps and ${unknownCnt} unknown apps. Failed to send ${failedCnt} messages`);

    });
  }

  return allMessagesPromise;

}

function topicsSend(topics, raixNotification) {
  let firebasePromises = [];

  topics.forEach(function (topic) {
    const fcmMessage = getFcmMessageForTopic(topic, raixNotification);
    const appIdentifier = raixNotification.appIdentifier;
    firebasePromises.push(sendFcmMessage(appIdentifier, fcmMessage));
  });

  return Promise.all(firebasePromises.map(function (promise) {
    return promise.reflect();
  }));
}

function fcmSingleMessage(userFcmToken, raixNotification) {
  // https://firebase.google.com/docs/reference/admin/node/admin.messaging.Message
  let fcmMessage = getFcmMessageForIndividualDevice(userFcmToken, raixNotification);
  const appIdentifier = raixNotification.appIdentifier;
  return sendFcmMessage(appIdentifier, fcmMessage);
}

function sendFcmMessage(appIdentifier, fcmMessage) {
  if (!Match.test(appIdentifier, String)) {
    console.error('ERROR: PUSH.sendFCM - No appIdentifier found for notification: ', fcmMessage);
    return Promise.reject({
      code: 'push/no-app-identifier',
      message: `ERROR: PUSH.sendFCM - No appIdentifier found for notification:  ${JSON.stringify(fcmMessage)}`,
      fcmMessage: fcmMessage
    });
  }
  const firebaseApp = Push.FirebaseApps[appIdentifier];
  if (!firebaseApp) {
    console.error('ERROR: PUSH.sendFCM - No firebase app found for notification: ', fcmMessage);
    return Promise.reject({
      code: 'push/no-firebase-app-found',
      message: `ERROR: PUSH.sendFCM - No firebase app found for notification:  ${JSON.stringify(fcmMessage)}`,
      fcmMessage: fcmMessage
    });
  }

  return new Promise((resolve, reject) => {
    firebaseApp.messaging().send(fcmMessage)
      .then(result => {
        resolve(result);
      })
      .catch(reason => {
        reject(_.extend(reason, {fcmMessage: fcmMessage}));
      });
  });
}

function getFcmMessageForIndividualDevice(userFcmToken, raixNotification) {
  let fcmMessage = getFcmMessage(raixNotification);
  fcmMessage.token = userFcmToken;
  return fcmMessage;
}

function getFcmMessageForTopic(topicName, raixNotification) {
  let fcmMessage = getFcmMessage(raixNotification);
  fcmMessage.topic = topicName;
  return fcmMessage;
}

function getFcmMessage(raixNotification) {
  return {
    notification: {
      title: raixNotification.title,
      body: raixNotification.text
    },
    data: raixNotification.payload,
    android: {
      notification: {
        sound: raixNotification.gcm.sound
      }
    },
    apns: {
      headers: {
        'apns-expiration': moment.utc().add(30, 'days').unix()
      },
      payload: {
        aps: {
          sound: raixNotification.apn.sound,
          badge: raixNotification.badge
        }
      }
    }
  };
}

function resendEntireNotification(notification) {
  if (!notification.failedCount || notification.failedCount <= 5) {
    let failedCount = (notification.failedCount) ? notification.failedCount + 1 : 1;

    Push.notifications.update({_id: notification._id}, {
      $set: {
        sent: false,
        sending: false,
        failedCount: failedCount,
        // eslint-disable-next-line no-restricted-properties
        delayUntil: moment().add(Math.pow(2, failedCount), 'minutes').toDate()
      }
    });
  }
}

function handleFailedNotification(notification, reason) {
  // TODO @mj implement error handling and conditions
  resendFailedNotification(notification, reason.fcmMessage);
}

function resendFailedNotification(notification, fcmMessage) {
  // TODO @mj implement resend
}
<|endoftext|>


<commit_before>define([
], function(
) {
    "use strict";

    function PubSub() {
        this._callbacks = Object.create(null);
    }

    var _p = PubSub.prototype;

    _p.subscribe = function(event, callback) {
        var callbacks = this._callbacks[event];
        if(!callbacks)
            this._callbacks[event] = callbacks = [];
        callbacks.push(callback);
    };

    _p.publish = function(event /* , args, ... */) {
        var i, l
          , args = []
          , callbacks = this._callbacks[event] || [];
          ;
        for(i=1,l=arguments.length;i<l;i++)
            args.push(arguments[i]);
        for(i=0,l=callbacks.length;i<l;i++)
            callbacks[i].apply(null, args);
    };

    return PubSub;
});
<commit_msg>PubSub: add inline comments, change some naming<commit_after>define([
], function(
) {
    "use strict";
    /**
     * Simple module for signaling.
     *
     * On a call to `pubSub.publish("channel-name"[, optional, arg1, ... args])`
     * the callback of all subscriptions of "channel-name" will be invoked
     * with the optional arguments given to `publish`:
     *          `callback(optional, arg1, ... args)`
     *
     * The subscriptions are always invoked in the order of subscription.
     *
     * There's no way to cancel subscription yet.
     */
    function PubSub() {
        this._callbacks = Object.create(null);
    }

    var _p = PubSub.prototype;

    _p.subscribe = function(channel, callback) {
        var callbacks = this._callbacks[channel];
        if(!callbacks)
            this._callbacks[channel] = callbacks = [];
        callbacks.push(callback);
    };

    _p.publish = function(channel /* , args, ... */) {
        var i, l
          , args = []
          , callbacks = this._callbacks[channel] || []
          ;
        for(i=1,l=arguments.length;i<l;i++)
            args.push(arguments[i]);
        for(i=0,l=callbacks.length;i<l;i++)
            callbacks[i].apply(null, args);
    };

    return PubSub;
});
<|endoftext|>


<commit_before>/*
 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Cloudkick licenses this file to You 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.
 */

var sys = require('sys');
var uuid = require('node-uuid');

var async = require('async');
var sprintf = require('sprintf').sprintf;

var ps = require('util/pubsub');
var log = require('util/log');

var Service = require('services').Service;
var check = require('health');
var httpCheck = require('health/checks/http');

var SERVICE_NAME = 'Health';

/**
 * Class representing a single scheduled check.
 *
 * @param {Check} check Check object.
 * @param {Integer} interval Run interval in miliseconds.
 *
 * @constructor
 */
function ScheduledCheck(check, interval) {
  this.id = uuid();
  this.check = check;
  this.interval = interval || 90000;

  this.timeoutId = null;
  this.isPaused = false;
  this.isScheduled = false;
  this.lastRunDate = null;
}

/**
 * Health service class.

 * @constructor
 */
function Health() {
  Service.call(this, SERVICE_NAME);

  this.activeChecks = {};
}

sys.inherits(Health, Service);

/**
 * Start the service and re-schedule all the unscheduled (not paused) checks.
 */
Health.prototype.start = function() {
  Service.prototype.start.call(this);

  this.scheduleChecks();
};

/**
 * Stop the service and un-schedule all the non-paused scheduled checks.
 */
Health.prototype.stop = function() {
  Service.prototype.stop.call(this);

  for (var checkId in this.activeChecks) {
    if (this.activeChecks.hasOwnProperty(checkId)) {
      this.unscheduleCheck(this.activeChecks[checkId].id);
    }
  }
};

/**
 * Add a new check.
 *
 * @param {ScheduledCheck} scheduledCheck Sccheduled check.
 */
Health.prototype.addCheck = function(scheduledCheck) {
  var self = this;
  var timeoutId;

  this.activeChecks[scheduledCheck.id] = scheduledCheck;
  this._setInterval(scheduledCheck.id);
  this.activeChecks[scheduledCheck.id].isScheduled = true;

  log.info(sprintf('Check %s added and scheduled to run every %s seconds',
                   scheduledCheck.id, scheduledCheck.interval / 1000));
};

/**
 * Remove and unschedule a check
 *
 * @param {Integer} id Check ID.
 * @return {Boolean} If check was removed.
 */
Health.prototype.removeCheck = function(id) {
  if (!this._checkExists(id)) {
    throw new Error(sprintf('Check with id %s not found', id));
  }

  if (this.activeChecks[id].isScheduled && this.activeChecks[id].timeoutId) {
    this._clearInterval(id);
    this.activeChecks[id].isScheduled = false;
  }

  if (delete this.activeChecks[id]) {
    log.info(sprintf('Removed check with id %s', id));

    return true;
  }

  return false;
};

/**
 * Run a check
 *
 * @param {Integer} id Check ID.
 */
Health.prototype.runCheck = function(id) {
  this.activeChecks[id].check.run();
  this.activeChecks[id].lastRunDate = new Date();
};

/**
 *  Schedule all the unscheduled checks.
 *
 * @param {Boolean} includePaused True to include the paused checks.
 */
Health.prototype.scheduleChecks = function(includePaused) {
  for (var checkId in this.activeChecks) {
    if (this.activeChecks.hasOwnProperty(checkId)) {
      if (!includePaused && this.activeChecks[checkId].isPaused) {
        continue;
      }

      this.scheduleCheck(checkId);
    }
  }
};

/**
 *  Unschedule all the scheduled checks.
 */
Health.prototype.unscheduleChecks = function() {
  for (var checkId in this.activeChecks) {
    if (this.activeChecks.hasOwnProperty(checkId)) {
      this.unscheduleCheck(checkId);
    }
  }
};

/**
 * Schedule a check.
 *
 * @param {Integer} id Check ID.
 */
Health.prototype.scheduleCheck = function(id) {
  if (!this.activeChecks[id].isScheduled && !this.activeChecks[id].timeoutId) {
    this._setInterval(id);
    this.activeChecks[id].isScheduled = true;
  }
};

/**
 * Unschedule a check.
 *
 * @param {Integer} id Check ID.
 */
Health.prototype.unscheduleCheck = function(id) {
  if (this.activeChecks[id].isScheduled && this.activeChecks[id].timeoutId) {
    this._clearInterval(id);
    this.activeChecks[id].isScheduled = false;
  }
};

/**
 * Pause a check.
 *
 * @param {Integer} id Check ID.
 */
Health.prototype.pauseCheck = function(id) {
  if (!this._checkExists(id)) {
    throw new Error(sprintf('Check with id %s not found', id));
  }

  if (this.activeChecks[id].isScheduled && this.activeChecks[id].timeoutId && !this.isPaused) {
    this._clearInterval(id);
    this.activeChecks[id].isScheduled = false;
    this.activeChecks[id].isPaused = true;
  }
};

/**
 * Resume a check.
 *
 * @param {Integer} id Check ID.
 */
Health.prototype.resumeCheck = function(id) {
  if (!this._checkExists(id)) {
    throw new Error(sprintf('Check with id %s not found', id));
  }

  if (!this.activeChecks[id].isScheduled && !this.activeChecks[id].timeoutId && this.isPaused) {
    this._setInterval(id);
    this.activeChecks[id].isScheduled = true;
    this.activeChecks[id].isPaused = false;
  }
};

/**
 * Return all the active checks.
 *
 * @return {Array} Array of ScheduledCheck objects.
 */
Health.prototype.getChecksArray = function() {
  var result = [];
  for (var checkId in this.activeChecks) {
    if (this.activeChecks.hasOwnProperty(checkId)) {
      result.push(this.activeChecks[checkId]);
    }
  }

  return result;
};

Health.prototype._checkExists = function(id) {
  if (!this.activeChecks.hasOwnProperty(id)) {
    return false;
  }

  return true;
};

Health.prototype._setInterval = function(id) {
  var timeoutId;
  var self = this;

  timeoutId = setInterval(function() { self.runCheck(self.activeChecks[id].id); },
                                         self.activeChecks[id].interval);
  this.activeChecks[id].timeoutId = timeoutId;
};

Health.prototype._clearInterval = function(id) {
  clearTimeout(this.activeChecks[id].timeoutId);
  this.activeChecks[id].timeoutId = null;
};

var health = new Health();

function load() {
  function startHealth() {
    health.start();

    ps.emit('cast.agent.services.health.started');
    log.info('health service started');
  }

   function stopHealth() {
     health.stop();

     ps.emit('cast.agent.services.health.stopped');
     log.info('health service stopped');
   }

   ps.on(ps.AGENT_STATE_START, function() {
     if (!health.isRunning) {
       startHealth();
     }
   });

   ps.on(ps.AGENT_STATE_STOP, function() {
    if (health.isRunning) {
      stopHealth();
    }
   });
}

exports.load = load;
exports.health = health;
<commit_msg>Export ScheduledCheck.<commit_after>/*
 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Cloudkick licenses this file to You 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.
 */

var sys = require('sys');
var uuid = require('node-uuid');

var async = require('async');
var sprintf = require('sprintf').sprintf;

var ps = require('util/pubsub');
var log = require('util/log');

var Service = require('services').Service;
var check = require('health');
var httpCheck = require('health/checks/http');

var SERVICE_NAME = 'Health';

/**
 * Class representing a single scheduled check.
 *
 * @param {Check} check Check object.
 * @param {Integer} interval Run interval in miliseconds.
 *
 * @constructor
 */
function ScheduledCheck(check, interval) {
  this.id = uuid();
  this.check = check;
  this.interval = interval || 90000;

  this.timeoutId = null;
  this.isPaused = false;
  this.isScheduled = false;
  this.lastRunDate = null;
}

/**
 * Health service class.

 * @constructor
 */
function Health() {
  Service.call(this, SERVICE_NAME);

  this.activeChecks = {};
}

sys.inherits(Health, Service);

/**
 * Start the service and re-schedule all the unscheduled (not paused) checks.
 */
Health.prototype.start = function() {
  Service.prototype.start.call(this);

  this.scheduleChecks();
};

/**
 * Stop the service and un-schedule all the non-paused scheduled checks.
 */
Health.prototype.stop = function() {
  Service.prototype.stop.call(this);

  for (var checkId in this.activeChecks) {
    if (this.activeChecks.hasOwnProperty(checkId)) {
      this.unscheduleCheck(this.activeChecks[checkId].id);
    }
  }
};

/**
 * Add a new check.
 *
 * @param {ScheduledCheck} scheduledCheck Sccheduled check.
 */
Health.prototype.addCheck = function(scheduledCheck) {
  var self = this;
  var timeoutId;

  this.activeChecks[scheduledCheck.id] = scheduledCheck;
  this._setInterval(scheduledCheck.id);
  this.activeChecks[scheduledCheck.id].isScheduled = true;

  log.info(sprintf('Check %s added and scheduled to run every %s seconds',
                   scheduledCheck.id, scheduledCheck.interval / 1000));
};

/**
 * Remove and unschedule a check
 *
 * @param {Integer} id Check ID.
 * @return {Boolean} If check was removed.
 */
Health.prototype.removeCheck = function(id) {
  if (!this._checkExists(id)) {
    throw new Error(sprintf('Check with id %s not found', id));
  }

  if (this.activeChecks[id].isScheduled && this.activeChecks[id].timeoutId) {
    this._clearInterval(id);
    this.activeChecks[id].isScheduled = false;
  }

  if (delete this.activeChecks[id]) {
    log.info(sprintf('Removed check with id %s', id));

    return true;
  }

  return false;
};

/**
 * Run a check
 *
 * @param {Integer} id Check ID.
 */
Health.prototype.runCheck = function(id) {
  this.activeChecks[id].check.run();
  this.activeChecks[id].lastRunDate = new Date();
};

/**
 *  Schedule all the unscheduled checks.
 *
 * @param {Boolean} includePaused True to include the paused checks.
 */
Health.prototype.scheduleChecks = function(includePaused) {
  for (var checkId in this.activeChecks) {
    if (this.activeChecks.hasOwnProperty(checkId)) {
      if (!includePaused && this.activeChecks[checkId].isPaused) {
        continue;
      }

      this.scheduleCheck(checkId);
    }
  }
};

/**
 *  Unschedule all the scheduled checks.
 */
Health.prototype.unscheduleChecks = function() {
  for (var checkId in this.activeChecks) {
    if (this.activeChecks.hasOwnProperty(checkId)) {
      this.unscheduleCheck(checkId);
    }
  }
};

/**
 * Schedule a check.
 *
 * @param {Integer} id Check ID.
 */
Health.prototype.scheduleCheck = function(id) {
  if (!this.activeChecks[id].isScheduled && !this.activeChecks[id].timeoutId) {
    this._setInterval(id);
    this.activeChecks[id].isScheduled = true;
  }
};

/**
 * Unschedule a check.
 *
 * @param {Integer} id Check ID.
 */
Health.prototype.unscheduleCheck = function(id) {
  if (this.activeChecks[id].isScheduled && this.activeChecks[id].timeoutId) {
    this._clearInterval(id);
    this.activeChecks[id].isScheduled = false;
  }
};

/**
 * Pause a check.
 *
 * @param {Integer} id Check ID.
 */
Health.prototype.pauseCheck = function(id) {
  if (!this._checkExists(id)) {
    throw new Error(sprintf('Check with id %s not found', id));
  }

  if (this.activeChecks[id].isScheduled && this.activeChecks[id].timeoutId && !this.isPaused) {
    this._clearInterval(id);
    this.activeChecks[id].isScheduled = false;
    this.activeChecks[id].isPaused = true;
  }
};

/**
 * Resume a check.
 *
 * @param {Integer} id Check ID.
 */
Health.prototype.resumeCheck = function(id) {
  if (!this._checkExists(id)) {
    throw new Error(sprintf('Check with id %s not found', id));
  }

  if (!this.activeChecks[id].isScheduled && !this.activeChecks[id].timeoutId && this.isPaused) {
    this._setInterval(id);
    this.activeChecks[id].isScheduled = true;
    this.activeChecks[id].isPaused = false;
  }
};

/**
 * Return all the active checks.
 *
 * @return {Array} Array of ScheduledCheck objects.
 */
Health.prototype.getChecksArray = function() {
  var result = [];
  for (var checkId in this.activeChecks) {
    if (this.activeChecks.hasOwnProperty(checkId)) {
      result.push(this.activeChecks[checkId]);
    }
  }

  return result;
};

Health.prototype._checkExists = function(id) {
  if (!this.activeChecks.hasOwnProperty(id)) {
    return false;
  }

  return true;
};

Health.prototype._setInterval = function(id) {
  var timeoutId;
  var self = this;

  timeoutId = setInterval(function() { self.runCheck(self.activeChecks[id].id); },
                                         self.activeChecks[id].interval);
  this.activeChecks[id].timeoutId = timeoutId;
};

Health.prototype._clearInterval = function(id) {
  clearTimeout(this.activeChecks[id].timeoutId);
  this.activeChecks[id].timeoutId = null;
};

var health = new Health();

function load() {
  function startHealth() {
    health.start();

    ps.emit('cast.agent.services.health.started');
    log.info('health service started');
  }

   function stopHealth() {
     health.stop();

     ps.emit('cast.agent.services.health.stopped');
     log.info('health service stopped');
   }

   ps.on(ps.AGENT_STATE_START, function() {
     if (!health.isRunning) {
       startHealth();
     }
   });

   ps.on(ps.AGENT_STATE_STOP, function() {
    if (health.isRunning) {
      stopHealth();
    }
   });
}

exports.load = load;
exports.ScheduledCheck = ScheduledCheck;
exports.health = health;
<|endoftext|>


<commit_before>'use strict'

const miss = require('mississippi')
const getSaksnummer = require('tfk-saksbehandling-get-saksnummer')
const getTemplatePath = require('tfk-saksbehandling-organisasjon-tilskudd-templates')
const datePadding = require('./date-padding')
const chooseReplyTemplate = require('./choose-reply-template')
const generateSammendrag = require('./generate-synopsis')
const getCaseType = require('./get-case-type')

function getMidlerType (item) {
  let type = 'Folkehelse'

  if (item.artform && item.artform.artform) {
    type = item.artform.artform
  }

  return type
}

module.exports = miss.through((chunk, encoding, callback) => {
  var item = JSON.parse(chunk)
  const midlerType = getMidlerType(item)
  const caseType = getCaseType(item)
  const caseData = getSaksnummer(caseType)

  var documentIn = {
    distribution: false,
    template: getTemplatePath('soknad'),
    type: 'Dokument inn',
    role: 'Mottaker'
  }

  var documentOut = {
    distribution: true,
    template: getTemplatePath(chooseReplyTemplate(item)),
    type: 'Dokument ut',
    role: 'Avsender'
  }
  const now = new Date()
  const then = new Date(parseInt(item.timeStamp, 10))
  const date = datePadding(now.getDate()) + '.' + datePadding(now.getMonth() + 1) + '.' + now.getFullYear()
  const ourDate = datePadding(then.getDate()) + '.' + datePadding(then.getMonth() + 1) + '.' + then.getFullYear()
  const data = {
    dato: date,
    datoSoknad: ourDate,
    navn: item.brregData.navn,
    adresse: item.brregData.forretningsadr || '',
    postnr: item.brregData.forradrpostnr,
    poststed: item.brregData.forradrpoststed,
    sammendrag: generateSammendrag(item),
    saksnummer: caseData.saksnummer
  }

  console.log(`${item._id}: setup-templates - ${item.brregData.navn}`)

  documentIn.data = data
  documentOut.data = data

  documentIn.title = `Søknad tilskuddsmidler - ${midlerType} - ${item.tiltak.navn}`
  documentIn.offTitle = `Søknad tilskuddsmidler - ${midlerType} - ${item.tiltak.navn}`

  documentOut.title = `Bekreftelse på mottatt søknad - ${item.tiltak.navn}`
  documentOut.offTitle = `Bekreftelse på mottatt søknad - ${item.tiltak.navn}`

  if (item.documentTemplates) {
    item.documentTemplates.push(documentIn)
    item.documentTemplates.push(documentOut)
  } else {
    item.documentTemplates = [documentIn, documentOut]
  }

  return callback(null, JSON.stringify(item))
})
<commit_msg>Swap avsender and mottaker<commit_after>'use strict'

const miss = require('mississippi')
const getSaksnummer = require('tfk-saksbehandling-get-saksnummer')
const getTemplatePath = require('tfk-saksbehandling-organisasjon-tilskudd-templates')
const datePadding = require('./date-padding')
const chooseReplyTemplate = require('./choose-reply-template')
const generateSammendrag = require('./generate-synopsis')
const getCaseType = require('./get-case-type')

function getMidlerType (item) {
  let type = 'Folkehelse'

  if (item.artform && item.artform.artform) {
    type = item.artform.artform
  }

  return type
}

module.exports = miss.through((chunk, encoding, callback) => {
  var item = JSON.parse(chunk)
  const midlerType = getMidlerType(item)
  const caseType = getCaseType(item)
  const caseData = getSaksnummer(caseType)

  var documentIn = {
    distribution: false,
    template: getTemplatePath('soknad'),
    type: 'Dokument inn',
    role: 'Avsender'
  }

  var documentOut = {
    distribution: true,
    template: getTemplatePath(chooseReplyTemplate(item)),
    type: 'Dokument ut',
    role: 'Mottaker'
  }
  const now = new Date()
  const then = new Date(parseInt(item.timeStamp, 10))
  const date = datePadding(now.getDate()) + '.' + datePadding(now.getMonth() + 1) + '.' + now.getFullYear()
  const ourDate = datePadding(then.getDate()) + '.' + datePadding(then.getMonth() + 1) + '.' + then.getFullYear()
  const data = {
    dato: date,
    datoSoknad: ourDate,
    navn: item.brregData.navn,
    adresse: item.brregData.forretningsadr || '',
    postnr: item.brregData.forradrpostnr,
    poststed: item.brregData.forradrpoststed,
    sammendrag: generateSammendrag(item),
    saksnummer: caseData.saksnummer
  }

  console.log(`${item._id}: setup-templates - ${item.brregData.navn}`)

  documentIn.data = data
  documentOut.data = data

  documentIn.title = `Søknad tilskuddsmidler - ${midlerType} - ${item.tiltak.navn}`
  documentIn.offTitle = `Søknad tilskuddsmidler - ${midlerType} - ${item.tiltak.navn}`

  documentOut.title = `Bekreftelse på mottatt søknad - ${item.tiltak.navn}`
  documentOut.offTitle = `Bekreftelse på mottatt søknad - ${item.tiltak.navn}`

  if (item.documentTemplates) {
    item.documentTemplates.push(documentIn)
    item.documentTemplates.push(documentOut)
  } else {
    item.documentTemplates = [documentIn, documentOut]
  }

  return callback(null, JSON.stringify(item))
})
<|endoftext|>


<commit_before>if (!dp) var dp = {};

dp.ui = {
    _initiated: false,
    init: function(obj) {
        if (!obj && dp.ui._initiated) return;
        dp.ui._initiated = true;
        dp.ui.element.init(obj);
    },
    util: {
        _alert_template: undefined,
        alert_focus: undefined,
        alert: function(a, b, c, d, e) {
            if (dp.ui.util._alert_template == undefined) {
                dp.ui.util._alert_template = dp_jqlib('
').addClass('dp4css-alert');
                dp.ui.util._alert_template.append(dp_jqlib('
').addClass('_bg'));

                var _box = dp_jqlib('
').addClass('_box');

                _box.append(dp_jqlib('
').addClass('_msg'));
                _box.append(dp_jqlib('
').addClass('_ico').text('!'));
                _box.append(dp_jqlib('
').addClass('_btn'));

                dp.ui.util._alert_template.append(_box);
            }

            var template = dp.ui.util._alert_template.clone();
            template.attr('id', 'uniqid-' + dp.helper.string.uniqid());

            var bg = template.find('._bg');
            var message = template.find('._msg');
            var button = template.find('._btn');
            var box = template.find('._box');

            var _msg = undefined;
            var _msg_html = undefined;
            var _buttons = [];

            if (typeof(a) == 'object' && !b && !c && !d && !e) {
                if (a.message && a.html) {
                    _msg_html = a.message;
                }
                else if (a.message) {
                    _msg = a.message;
                }

                if (a.buttons) {
                    for (var i in a.buttons) {
                        _buttons.push([a.buttons[i][0], a.buttons[i][1]]);
                    }
                }
            }
            else if (typeof(a) == 'string' && !b && !c && !d && !e) {
                _msg = a;

                if (b === null) {

                }
                else {
                    _buttons.push(['OK', undefined]);
                }
            }
            else if (typeof(a) == 'string' && typeof(b) == 'string' && !c && !d && !e) {
                _msg = a;
                _buttons.push([b, undefined]);
            }
            else if (typeof(a) == 'string' && typeof(b) == 'function' && !c && !d && !e) {
                _msg = a;
                _buttons.push(['OK', b]);
            }
            else if (typeof(a) == 'string' && typeof(b) == 'function' && typeof(c) == 'string' && !d && !e) {
                _msg = a;
                _buttons.push([c, b]);
            }
            else if (typeof(a) == 'string' && typeof(b) == 'function' && typeof(c) == 'function' && !d && !e) {
                _msg = a;
                _buttons.push(['OK', b]);
                _buttons.push(['Cancel', c]);
            }
            else if (typeof(a) == 'string' && typeof(b) == 'function' && typeof(c) == 'string' && typeof(d) == 'function' && !e) {
                _msg = a;
                _buttons.push([c, b]);
                _buttons.push(['Cancel', d]);
            }
            else if (typeof(a) == 'string' && typeof(b) == 'function' && typeof(c) == 'string' && typeof(d) == 'function' && typeof(e) == 'string') {
                _msg = a;
                _buttons.push([c, b]);
                _buttons.push([e, d]);
            }

            if (_msg_html) {
                message.html(_msg_html);
            }
            else if (_msg) {
                message.text(_msg);
                message.html(message.text().replace(/\n/gi, '
'));
            }

            var box_height = 0;
            var pongdang = 15;
            var delegate = undefined;

            var _dismiss = function() {
                box.animate({
                    'margin-top': (((box_height / 2) + 30) * -1) + 'px'
                }, 150, function() {
                    box.animate({
                        'margin-top': (((box_height / 2) - 70) * -1) + 'px',
                        'opacity': 0
                    }, 150);

                    bg.fadeTo(300, 0, function() {
                        template.remove();

                        if (dp.ui.util.alert_focus) {
                            dp.ui.util.alert_focus.focus();
                            dp.ui.util.alert_focus = undefined;
                        }

                        if (delegate) {
                            delegate(template);
                        }
                    });
                });
            };

            dp_jqlib(_buttons).each(function(k, e) {
                var btn = dp_jqlib('<button />');
                btn.addClass('_i-' + k);
                btn.text(e[0]);
                btn.click(function() {
                    if (e[1] && typeof(e[1]) == 'function') {
                        delegate = e[1];
                    }

                    _dismiss();
                });

                button.append(btn);
            });

            dp_jqlib('body').append(template);

            box_height = box.height();

            var bg_opacity = parseFloat(bg.css('opacity'));

            bg.fadeTo(0, 0.001);
            box.fadeTo(0, 0.001);
            box.css('margin-top', (((box_height / 2) + 70) * -1) + 'px');

            bg.fadeTo(200, bg_opacity);
            box.delay(50).animate({
                'margin-top': (((box_height / 2) - 10) * -1) + 'px',
                'opacity': 1.0
            }, 200, function() {
                box.animate({
                    'margin-top': ((box_height / 2 + 5) * -1) + 'px'
                }, 150, function() {
                    box.animate({
                        'margin-top': ((box_height / 2) * -1) + 'px'
                    }, 150);
                });
            });

            if (button) {
                button.find('._i-0').focus();
            }

            return template;
        },
        noti: function(a, b, c, d) {
            alert('not implemented yet.');
        }
    },
    element: {
        init: function(obj) {
            dp.ui.element.input.delegate.on_return(obj);
            dp.ui.element.input.delegate.on_focus(obj);
        },
        input: {
            delegate: {
                on_return: function(obj) {
                    if (!obj) obj = dp_jqlib('body');
                    obj.find('input[dp-on-return][dp-on-return-installed!=yes]').each(function() {
                        var _id = dp_jqlib(this).attr('id') || 'uniqid-' + dp.helper.string.uniqid();

                        dp_jqlib(this).attr('id', _id);
                        dp_jqlib(this).attr('dp-on-return-installed', 'yes');

                        dp_jqlib(this).keypress(function(e) {
                            if (dp_jqlib(this).attr('dp-on-return-busy') == 'yes') {
                                return;
                            }

                            if (e.keyCode == 13) {
                                dp_jqlib(this).attr('dp-on-return-busy', 'yes');
                                setTimeout(dp_jqlib(this).attr('dp-on-return'), 0);
                                setTimeout("dp_jqlib('#" + _id + "').attr('dp-on-return-busy', 'no');", 150);

                                e.preventDefault();
                            }
                        });
                    });
                },
                on_focus: function(obj) {
                    if (!obj) obj = dp_jqlib('body');
                    obj.find('input[dp-on-focus][dp-on-focus-installed!=yes]').each(function() {
                        var _id = dp_jqlib(this).attr('id') || 'uniqid-' + dp.helper.string.uniqid();

                        dp_jqlib(this).attr('id', _id);
                        dp_jqlib(this).attr('dp-on-focus-installed', 'yes');

                        dp_jqlib(this).focus(function(e) {
                            setTimeout(dp_jqlib(this).attr('dp-on-focus'), 0);
                        });
                    });
                }
            }
        }
    }
};

dp_init(function() {
    dp.init();
});<commit_msg>fixed delegate as string.<commit_after>if (!dp) var dp = {};

dp.ui = {
    _initiated: false,
    init: function(obj) {
        if (!obj && dp.ui._initiated) return;
        dp.ui._initiated = true;
        dp.ui.element.init(obj);
    },
    util: {
        _alert_template: undefined,
        alert_focus: undefined,
        alert: function(a, b, c, d, e) {
            if (dp.ui.util._alert_template == undefined) {
                dp.ui.util._alert_template = dp_jqlib('
').addClass('dp4css-alert');
                dp.ui.util._alert_template.append(dp_jqlib('
').addClass('_bg'));

                var _box = dp_jqlib('
').addClass('_box');

                _box.append(dp_jqlib('
').addClass('_msg'));
                _box.append(dp_jqlib('
').addClass('_ico').text('!'));
                _box.append(dp_jqlib('
').addClass('_btn'));

                dp.ui.util._alert_template.append(_box);
            }

            var template = dp.ui.util._alert_template.clone();
            template.attr('id', 'uniqid-' + dp.helper.string.uniqid());

            var bg = template.find('._bg');
            var message = template.find('._msg');
            var button = template.find('._btn');
            var box = template.find('._box');

            var _msg = undefined;
            var _msg_html = undefined;
            var _buttons = [];

            if (typeof(a) == 'object' && !b && !c && !d && !e) {
                if (a.message && a.html) {
                    _msg_html = a.message;
                }
                else if (a.message) {
                    _msg = a.message;
                }

                if (a.buttons) {
                    for (var i in a.buttons) {
                        _buttons.push([a.buttons[i][0], a.buttons[i][1]]);
                    }
                }
            }
            else if (typeof(a) == 'string' && !b && !c && !d && !e) {
                _msg = a;

                if (b === null) {

                }
                else {
                    _buttons.push(['OK', undefined]);
                }
            }
            else if (typeof(a) == 'string' && typeof(b) == 'string' && !c && !d && !e) {
                _msg = a;
                _buttons.push([b, undefined]);
            }
            else if (typeof(a) == 'string' && typeof(b) == 'function' && !c && !d && !e) {
                _msg = a;
                _buttons.push(['OK', b]);
            }
            else if (typeof(a) == 'string' && typeof(b) == 'function' && typeof(c) == 'string' && !d && !e) {
                _msg = a;
                _buttons.push([c, b]);
            }
            else if (typeof(a) == 'string' && typeof(b) == 'function' && typeof(c) == 'function' && !d && !e) {
                _msg = a;
                _buttons.push(['OK', b]);
                _buttons.push(['Cancel', c]);
            }
            else if (typeof(a) == 'string' && typeof(b) == 'function' && typeof(c) == 'string' && typeof(d) == 'function' && !e) {
                _msg = a;
                _buttons.push([c, b]);
                _buttons.push(['Cancel', d]);
            }
            else if (typeof(a) == 'string' && typeof(b) == 'function' && typeof(c) == 'string' && typeof(d) == 'function' && typeof(e) == 'string') {
                _msg = a;
                _buttons.push([c, b]);
                _buttons.push([e, d]);
            }

            if (_msg_html) {
                message.html(_msg_html);
            }
            else if (_msg) {
                message.text(_msg);
                message.html(message.text().replace(/\n/gi, '
'));
            }

            var box_height = 0;
            var pongdang = 15;
            var delegate = undefined;

            var _dismiss = function() {
                box.animate({
                    'margin-top': (((box_height / 2) + 30) * -1) + 'px'
                }, 150, function() {
                    box.animate({
                        'margin-top': (((box_height / 2) - 70) * -1) + 'px',
                        'opacity': 0
                    }, 150);

                    bg.fadeTo(300, 0, function() {
                        template.remove();

                        if (dp.ui.util.alert_focus) {
                            dp.ui.util.alert_focus.focus();
                            dp.ui.util.alert_focus = undefined;
                        }

                        if (delegate) {
                            delegate(template);
                        }
                    });
                });
            };

            dp_jqlib(_buttons).each(function(k, e) {
                var btn = dp_jqlib('<button />');
                btn.addClass('_i-' + k);
                btn.text(e[0]);
                btn.click(function() {
                    if (e[1] && typeof(e[1]) == 'function') {
                        delegate = e[1];
                    }

                    _dismiss();
                });

                button.append(btn);
            });

            dp_jqlib('body').append(template);

            box_height = box.height();

            var bg_opacity = parseFloat(bg.css('opacity'));

            bg.fadeTo(0, 0.001);
            box.fadeTo(0, 0.001);
            box.css('margin-top', (((box_height / 2) + 70) * -1) + 'px');

            bg.fadeTo(200, bg_opacity);
            box.delay(50).animate({
                'margin-top': (((box_height / 2) - 10) * -1) + 'px',
                'opacity': 1.0
            }, 200, function() {
                box.animate({
                    'margin-top': ((box_height / 2 + 5) * -1) + 'px'
                }, 150, function() {
                    box.animate({
                        'margin-top': ((box_height / 2) * -1) + 'px'
                    }, 150);
                });
            });

            if (button) {
                button.find('._i-0').focus();
            }

            return template;
        },
        noti: function(a, b, c, d) {
            alert('not implemented yet.');
        }
    },
    element: {
        init: function(obj) {
            dp.ui.element.input.delegate.on_return(obj);
            dp.ui.element.input.delegate.on_focus(obj);
        },
        input: {
            delegate: {
                on_return: function(obj) {
                    if (!obj) obj = dp_jqlib('body');
                    obj.find('input[dp-on-return][dp-on-return-installed!=yes]').each(function() {
                        var _id = dp_jqlib(this).attr('id') || 'uniqid-' + dp.helper.string.uniqid();

                        dp_jqlib(this).attr('id', _id);
                        dp_jqlib(this).attr('dp-on-return-installed', 'yes');

                        dp_jqlib(this).keypress(function(e) {
                            if (dp_jqlib(this).attr('dp-on-return-busy') == 'yes') {
                                return;
                            }

                            if (e.keyCode == 13) {
                                var delegate;

                                try {
                                    delegate = eval(dp_jqlib(this).attr('dp-on-return'));
                                }catch(_){}

                                if (delegate && typeof(delegate) == 'function') {
                                    dp_jqlib(this).attr('dp-on-return-busy', 'yes');
                                    setTimeout(delegate(dp_jqlib(this)), 0);
                                    setTimeout("dp_jqlib('#" + _id + "').attr('dp-on-return-busy', 'no');", 150);

                                    e.preventDefault();
                                }
                            }
                        });
                    });
                },
                on_focus: function(obj) {
                    if (!obj) obj = dp_jqlib('body');
                    obj.find('input[dp-on-focus][dp-on-focus-installed!=yes]').each(function() {
                        var _id = dp_jqlib(this).attr('id') || 'uniqid-' + dp.helper.string.uniqid();

                        dp_jqlib(this).attr('id', _id);
                        dp_jqlib(this).attr('dp-on-focus-installed', 'yes');

                        dp_jqlib(this).focus(function(e) {
                            var delegate;

                            try {
                                delegate = eval(dp_jqlib(this).attr('dp-on-focus'));
                            }catch(_){}

                            if (delegate && typeof(delegate) == 'function') {
                                setTimeout(delegate(dp_jqlib(this)), 0);
                            }
                        });
                    });
                }
            }
        }
    }
};

dp_init(function() {
    dp.init();
});
<|endoftext|>


<commit_before>/**
 * Copyright 2013-2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule RelayPublic
 * @typechecks
 * @flow
 */

'use strict';

var RelayContainer = require('RelayContainer');
var RelayMutation = require('RelayMutation');
var RelayNetworkLayer = require('RelayNetworkLayer');
var RelayPropTypes = require('RelayPropTypes');
var RelayQL = require('RelayQL');
var RelayRootContainer = require('RelayRootContainer');
var RelayRoute = require('RelayRoute');
var RelayStore = require('RelayStore');
var RelayTaskScheduler = require('RelayTaskScheduler');

var getRelayQueries = require('getRelayQueries');
var isRelayContainer = require('isRelayContainer');

/**
 * Relay contains the set of public methods used to initialize and orchestrate
 * a React application that uses GraphQL to declare data dependencies.
 */
var RelayPublic = {
  Mutation: RelayMutation,
  PropTypes: RelayPropTypes,
  QL: RelayQL,
  RootContainer: RelayRootContainer,
  Route: RelayRoute,
  Store: RelayStore,

  createContainer: RelayContainer.create,
  getQueries: getRelayQueries,
  injectNetworkLayer: RelayNetworkLayer.injectNetworkLayer,
  injectTaskScheduler: RelayTaskScheduler.injectScheduler,
  isContainer: isRelayContainer,
};

module.exports = RelayPublic;
<commit_msg>expose relay internals on devtools global<commit_after>/**
 * Copyright 2013-2015, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule RelayPublic
 * @typechecks
 * @flow
 */

'use strict';

var RelayContainer = require('RelayContainer');
var RelayMutation = require('RelayMutation');
var RelayNetworkLayer = require('RelayNetworkLayer');
var RelayPropTypes = require('RelayPropTypes');
var RelayQL = require('RelayQL');
var RelayRootContainer = require('RelayRootContainer');
var RelayRoute = require('RelayRoute');
var RelayStore = require('RelayStore');
var RelayTaskScheduler = require('RelayTaskScheduler');

var getRelayQueries = require('getRelayQueries');
var isRelayContainer = require('isRelayContainer');

if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined') {
  __REACT_DEVTOOLS_GLOBAL_HOOK__._relayInternals = {
    NetworkLayer: require('RelayNetworkLayer'),
    DefaultStoreData: require('RelayStoreData').getDefaultInstance(),
  };
}

/**
 * Relay contains the set of public methods used to initialize and orchestrate
 * a React application that uses GraphQL to declare data dependencies.
 */
var RelayPublic = {
  Mutation: RelayMutation,
  PropTypes: RelayPropTypes,
  QL: RelayQL,
  RootContainer: RelayRootContainer,
  Route: RelayRoute,
  Store: RelayStore,

  createContainer: RelayContainer.create,
  getQueries: getRelayQueries,
  injectNetworkLayer: RelayNetworkLayer.injectNetworkLayer,
  injectTaskScheduler: RelayTaskScheduler.injectScheduler,
  isContainer: isRelayContainer,
};

module.exports = RelayPublic;
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import {slide as Menu} from 'react-burger-menu';
import {Link} from 'react-router-dom';
import ToggleShowAveragedSensors from "./ToggleShowAveragedSensors";
import './css/burger.css';
import 'font-awesome/css/font-awesome.min.css';

class SidebarMenu extends Component {

  render() {
    return (
      <Menu>
        <Link to='' onClick={ console.log('click a menu') }>Temperatures</Link>
        <ToggleShowAveragedSensors showAveragedSensors={ this.props.showAveragedSensors } onToggleShowAveragedSensors={ this.props.onToggleShowAveragedSensors } />
        <Link to='logs'>Logs</Link>
        <Link to='connections'>Live Connections</Link>
        { this.renderHref('http://temperature-machine.com/docs', '_blank', , 'Documentation')}
        { this.renderHref('http://github.com/tobyweston/temperature-machine', '_blank', , 'Github')}
        { this.renderHref('http://' + location.hostname + ':11900/temperatures.csv', '', , 'Export CSV')}
      </Menu>
    );
  }

  renderHref(url, target, icon, text) {
    return { icon }{ text }
  }
}

export default SidebarMenu;
<commit_msg>close menu on click / page transition<commit_after>import React, {Component} from 'react';
import {slide as Menu} from 'react-burger-menu';
import {Link} from 'react-router-dom';
import ToggleShowAveragedSensors from "./ToggleShowAveragedSensors";
import './css/burger.css';
import 'font-awesome/css/font-awesome.min.css';

class SidebarMenu extends Component {

  constructor (props) {
    super(props);
    this.state = {
      menuOpen: false
    }
  }

  handleStateChange(state) {
    this.setState({menuOpen: state.isOpen})
  }

  render() {
    return (
      <Menu isOpen={ this.state.menuOpen } onStateChange={ (state) => this.handleStateChange(state) }>
        <Link onClick={ () => this.closeMenu() } to=''>Temperatures</Link>
        <ToggleShowAveragedSensors showAveragedSensors={ this.props.showAveragedSensors } onToggleShowAveragedSensors={ this.props.onToggleShowAveragedSensors } />
        <Link onClick={ () => this.closeMenu() } to='logs'>Logs</Link>
        <Link onClick={ () => this.closeMenu() } to='connections'>Live Connections</Link>
        { this.renderHref('http://temperature-machine.com/docs', '_blank', , 'Documentation')}
        { this.renderHref('http://github.com/tobyweston/temperature-machine', '_blank', , 'Github')}
        { this.renderHref('http://' + location.hostname + ':11900/temperatures.csv', '', , 'Export CSV')}
      </Menu>
    );
  }

  renderHref(url, target, icon, text) {
    return { icon }{ text }
  }

  closeMenu () {
    this.setState({ menuOpen: false })
  }

}

export default SidebarMenu;
<|endoftext|>


<commit_before>var url = require('url')

module.exports = function path(req) {
  var path = url.parse(req.url).pathname
  if (!path || !~path.indexOf('/')) return null

  var base = path.split('/')[1]
  if (!base) return null

  var match = base.match(/^v?([0-9]+(\.[0-9]+)?(\.[0-9]+)?)$/i)
  if (match && match[1]) {
    return match[1]
  }

  return null
}
<commit_msg>refactor(path)<commit_after>var url = require('url')

module.exports = function path(req) {
  var path = url.parse(req.url).pathname
  if (!path || !~path.indexOf('/')) return null

  var base = path.split('/')[1]
  if (!base) return null

  var match = base.match(/^v?([0-9]+(\.[0-9]+)?(\.[0-9]+)?)$/i)
  if (match && match[1]) return match[1]

  return null
}
<|endoftext|>


<commit_before>import classNames from 'classnames';
import DOMUtil from '../Util/DOMUtil';
import React, {PropTypes} from 'react/addons';
import Util from '../Util/Util';
import VirtualList from '../VirtualList/VirtualList';

const MAX_CACHE_SIZE = 10000;

let sortData = (columns, data, sortBy) => {
  if (sortBy.order === undefined || sortBy.prop === undefined) {
    return data;
  }

  let sortFunction;

  columns.forEach((column) => {
    if (column.prop === sortBy.prop) {
      sortFunction = column.sortFunction;
    }
  });

  if (sortFunction) {
    // Use custom sort method if specified.
    data = Util.sortBy(data, sortFunction(sortBy.prop, sortBy.order));
  } else {
    // Otherwise, use default sorting.
    data = Util.sortBy(data, sortBy.prop);
  }

  if (sortBy.order === 'desc') {
    data.reverse();
  }

  return data;
};

let getClassName = (column, sortBy, data, columns) => {
  if (Util.isFunction(column.className)) {
    return column.className(
      column.prop, sortBy, data, columns
    );
  }

  return column.className || '';
};

export default class Table extends React.Component {
  constructor() {
    super();
    this.state = {
      sortBy: {}
    };
    this.cachedCells = {};
    this.cachedIDs = [];
    this.container = window;
  }

  componentWillMount() {
    if (this.props.sortBy.prop) {
      this.handleSort(this.props.sortBy.prop);
    }
  }

  componentDidMount() {
    this.updateHeight();
  }

  componentWillReceiveProps() {
    this.updateHeight();

    if (this.props.sortBy.prop) {
      this.handleSort(this.props.sortBy.prop, {toggle: false});
    }
  }

  checkFullCache() {
    if (this.cachedIDs.length < MAX_CACHE_SIZE) {
      return;
    }

    let lastID = this.cachedIDs.shift();
    delete this.cachedCells[lastID];
  }

  addToCache(id, element) {
    this.cachedCells[id] = element;
    this.cachedIDs.push(id);
    this.checkFullCache();
  }

  updateHeight() {
    let {props, state, refs} = this;

    if (props.containerSelector && this.container === window) {
      this.container = DOMUtil.closest(
        React.findDOMNode(this), props.containerSelector
      );
    }

    if (props.itemHeight == null &&
      state.itemHeight == null &&
      refs.itemHeightContainer != null) {
      // Calculate content height only once and when node is ready
      let itemHeight = DOMUtil.getComputedDimensions(
        React.findDOMNode(refs.itemHeightContainer).querySelector('tr')
      ).height;
      this.setState({itemHeight});
    }
  }

  buildUniqueID(cellClassName, cellValue, prop) {
    return prop + cellClassName + cellValue;
  }

  getHeaders(headers, sortBy) {
    let buildSortAttributes = (header) => {
      let sortEvent = this.handleSort.bind(this, header.prop);
      return {
        onClick: sortEvent,
        tabIndex: 0,
        'aria-sort': this.state.sortBy.order,
        'aria-label':
          `${header.prop}: activate to sort column ${this.state.sortBy.order}`
      };
    };

    return headers.map((header, index) => {
      let order, heading;
      let attributes = {};

      // Only add sorting events if the column has a value for 'prop'
      // and the 'sorting' property is true.
      if (header.sortable !== false && 'prop' in header) {
        attributes = buildSortAttributes(header);
        order = this.state.sortBy.order;
      }

      // If the heading property is a method, then pass to it the options and
      // render the result. Otherwise, display the value.
      if (Util.isFunction(header.heading)) {
        heading = header.heading(header.prop, order, sortBy);
      } else {
        heading = header.heading;
      }

      attributes.className = getClassName(
        header, this.state.sortBy, null, this.props.columns
      );
      attributes.key = index;

      return (

          {heading}

      );
    });
  }

  getBufferItem(columns, styles) {
    return

;
  }

  getEmptyRowCell(columns) {
    return (

          {this.props.emptyMessage}

    );
  }

  getRowCells(columns, sortBy, buildRowOptions, idAttribute, row) {
    let id = row[idAttribute];

    let rowCells = columns.map((column, index) => {
      let cellClassName = getClassName(column, sortBy, row, columns);
      let prop = column.prop;
      let cellValue = row[prop];
      let cellID;

      if (!column.dontCache) {
        cellID = this.buildUniqueID(cellClassName, cellValue, prop);
      }

      // Skip build cell if we have it memoized
      if (cellID === undefined || this.cachedCells[cellID] == null) {
        if (column.render) {
          cellValue = column.render(prop, row);
        }

        if (cellValue === undefined) {
          cellValue = column.defaultContent;
          cellClassName += ' empty-cell';
        }

        let cellElement = (

            {cellValue}

        );

        if (column.dontCache) {
          return cellElement;
        }

        this.addToCache(cellID, cellElement);
      }

      return this.cachedCells[cellID];
    });

    return (

        {rowCells}

    );
  }

  handleSort(prop, options) {
    let sortBy = this.state.sortBy;
    let onSortCallback = this.props.onSortCallback;
    options = Util.extend({
      toggle: true
    }, options);

    if (options.toggle) {
      let order;

      if (sortBy.order === 'desc' || sortBy.prop !== prop) {
        order = 'asc';
      } else {
        order = 'desc';
      }

      this.setState({
        sortBy: {order, prop}
      });
    }

    if (Util.isFunction(onSortCallback)) {
      onSortCallback(sortBy);
    }
  }

  getTBody(columns, data, sortBy, itemHeight, idAttribute) {
    let buildRowOptions = this.props.buildRowOptions;
    let childToMeasure;

    if (itemHeight === 0 && data.length) {
      childToMeasure = this.getRowCells(columns, sortBy, buildRowOptions, idAttribute, data[0]);
      return (

          {childToMeasure}

      );
    }

    if (data.length === 0) {
      return (

          {this.getEmptyRowCell(columns)}

      );
    }

    return (
      <VirtualList
        container={this.container}
        itemBuffer={70}
        itemHeight={itemHeight}
        items={sortData(columns, data, sortBy)}
        renderBufferItem={this.getBufferItem.bind(this, columns)}
        renderItem={this.getRowCells.bind(this, columns, sortBy, buildRowOptions, idAttribute)}
        scrollDelay={200}
        tagName="tbody" />
    );
  }

  render() {
    let {props, state} = this;
    let {columns, data, idAttribute} = props;
    let classes = classNames(props.className, 'flush-bottom');
    let sortBy = state.sortBy;
    let itemHeight = state.itemHeight || props.itemHeight || 0;

    return (

          {props.colGroup}

              {this.getHeaders(columns, sortBy)}

          {this.getTBody(columns, data, sortBy, itemHeight, idAttribute)}

    );
  }
}

Table.defaultProps = {
  buildRowOptions: () => { return {}; },
  sortBy: {},
  emptyMessage: 'No data'
};

Table.propTypes = {
  // Optional attributes to be passed to the row elements.
  buildRowOptions: PropTypes.func,

  className: PropTypes.string,

  // Optional colgroup component.
  colGroup: PropTypes.object,

  // Define how columns should be rendered and if they are sortable.
  columns: PropTypes.arrayOf(
    PropTypes.shape({
      attributes: React.PropTypes.object,
      className: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
      ]),
      defaultContent: PropTypes.string,
      heading: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
      ]).isRequired,
      prop: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      sortable: PropTypes.bool,
      // Custom sorting function. If this function returns null,
      // it will fallback to default sorting.
      sortFunction: PropTypes.func
    })
  ).isRequired,

  // Optional selector to use as container.
  containerSelector: PropTypes.string,

  // Data to display in the table.
  // Make sure to clone the data, cannot be modified!
  data: PropTypes.array.isRequired,

  // Text to show if there is no data.
  emptyMessage: PropTypes.string,

  // Optional item height for the scroll table. If not provided, it will render
  // once to measure the height of the first child.
  // NB: Initial render will stop any ongoing animation, if this is not provided
  itemHeight: PropTypes.number,

  // Provide what attribute in the data make a row unique.
  idAttribute: PropTypes.string.isRequired,

  // Optional callback function when sorting is complete.
  onSortCallback: PropTypes.func,

  // Optional default sorting criteria.
  sortBy: PropTypes.shape({
    order: PropTypes.oneOf(['asc', 'desc']),
    prop: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
  })
};
<commit_msg>add dontCache to Table proptypes<commit_after>import classNames from 'classnames';
import DOMUtil from '../Util/DOMUtil';
import React, {PropTypes} from 'react/addons';
import Util from '../Util/Util';
import VirtualList from '../VirtualList/VirtualList';

const MAX_CACHE_SIZE = 10000;

let sortData = (columns, data, sortBy) => {
  if (sortBy.order === undefined || sortBy.prop === undefined) {
    return data;
  }

  let sortFunction;

  columns.forEach((column) => {
    if (column.prop === sortBy.prop) {
      sortFunction = column.sortFunction;
    }
  });

  if (sortFunction) {
    // Use custom sort method if specified.
    data = Util.sortBy(data, sortFunction(sortBy.prop, sortBy.order));
  } else {
    // Otherwise, use default sorting.
    data = Util.sortBy(data, sortBy.prop);
  }

  if (sortBy.order === 'desc') {
    data.reverse();
  }

  return data;
};

let getClassName = (column, sortBy, data, columns) => {
  if (Util.isFunction(column.className)) {
    return column.className(
      column.prop, sortBy, data, columns
    );
  }

  return column.className || '';
};

export default class Table extends React.Component {
  constructor() {
    super();
    this.state = {
      sortBy: {}
    };
    this.cachedCells = {};
    this.cachedIDs = [];
    this.container = window;
  }

  componentWillMount() {
    if (this.props.sortBy.prop) {
      this.handleSort(this.props.sortBy.prop);
    }
  }

  componentDidMount() {
    this.updateHeight();
  }

  componentWillReceiveProps() {
    this.updateHeight();

    if (this.props.sortBy.prop) {
      this.handleSort(this.props.sortBy.prop, {toggle: false});
    }
  }

  checkFullCache() {
    if (this.cachedIDs.length < MAX_CACHE_SIZE) {
      return;
    }

    let lastID = this.cachedIDs.shift();
    delete this.cachedCells[lastID];
  }

  addToCache(id, element) {
    this.cachedCells[id] = element;
    this.cachedIDs.push(id);
    this.checkFullCache();
  }

  updateHeight() {
    let {props, state, refs} = this;

    if (props.containerSelector && this.container === window) {
      this.container = DOMUtil.closest(
        React.findDOMNode(this), props.containerSelector
      );
    }

    if (props.itemHeight == null &&
      state.itemHeight == null &&
      refs.itemHeightContainer != null) {
      // Calculate content height only once and when node is ready
      let itemHeight = DOMUtil.getComputedDimensions(
        React.findDOMNode(refs.itemHeightContainer).querySelector('tr')
      ).height;
      this.setState({itemHeight});
    }
  }

  buildUniqueID(cellClassName, cellValue, prop) {
    return prop + cellClassName + cellValue;
  }

  getHeaders(headers, sortBy) {
    let buildSortAttributes = (header) => {
      let sortEvent = this.handleSort.bind(this, header.prop);
      return {
        onClick: sortEvent,
        tabIndex: 0,
        'aria-sort': this.state.sortBy.order,
        'aria-label':
          `${header.prop}: activate to sort column ${this.state.sortBy.order}`
      };
    };

    return headers.map((header, index) => {
      let order, heading;
      let attributes = {};

      // Only add sorting events if the column has a value for 'prop'
      // and the 'sorting' property is true.
      if (header.sortable !== false && 'prop' in header) {
        attributes = buildSortAttributes(header);
        order = this.state.sortBy.order;
      }

      // If the heading property is a method, then pass to it the options and
      // render the result. Otherwise, display the value.
      if (Util.isFunction(header.heading)) {
        heading = header.heading(header.prop, order, sortBy);
      } else {
        heading = header.heading;
      }

      attributes.className = getClassName(
        header, this.state.sortBy, null, this.props.columns
      );
      attributes.key = index;

      return (

          {heading}

      );
    });
  }

  getBufferItem(columns, styles) {
    return

;
  }

  getEmptyRowCell(columns) {
    return (

          {this.props.emptyMessage}

    );
  }

  getRowCells(columns, sortBy, buildRowOptions, idAttribute, row) {
    let id = row[idAttribute];

    let rowCells = columns.map((column, index) => {
      let cellClassName = getClassName(column, sortBy, row, columns);
      let prop = column.prop;
      let cellValue = row[prop];
      let cellID;

      if (!column.dontCache) {
        cellID = this.buildUniqueID(cellClassName, cellValue, prop);
      }

      // Skip build cell if we have it memoized
      if (cellID === undefined || this.cachedCells[cellID] == null) {
        if (column.render) {
          cellValue = column.render(prop, row);
        }

        if (cellValue === undefined) {
          cellValue = column.defaultContent;
          cellClassName += ' empty-cell';
        }

        let cellElement = (

            {cellValue}

        );

        if (column.dontCache) {
          return cellElement;
        }

        this.addToCache(cellID, cellElement);
      }

      return this.cachedCells[cellID];
    });

    return (

        {rowCells}

    );
  }

  handleSort(prop, options) {
    let sortBy = this.state.sortBy;
    let onSortCallback = this.props.onSortCallback;
    options = Util.extend({
      toggle: true
    }, options);

    if (options.toggle) {
      let order;

      if (sortBy.order === 'desc' || sortBy.prop !== prop) {
        order = 'asc';
      } else {
        order = 'desc';
      }

      this.setState({
        sortBy: {order, prop}
      });
    }

    if (Util.isFunction(onSortCallback)) {
      onSortCallback(sortBy);
    }
  }

  getTBody(columns, data, sortBy, itemHeight, idAttribute) {
    let buildRowOptions = this.props.buildRowOptions;
    let childToMeasure;

    if (itemHeight === 0 && data.length) {
      childToMeasure = this.getRowCells(columns, sortBy, buildRowOptions, idAttribute, data[0]);
      return (

          {childToMeasure}

      );
    }

    if (data.length === 0) {
      return (

          {this.getEmptyRowCell(columns)}

      );
    }

    return (
      <VirtualList
        container={this.container}
        itemBuffer={70}
        itemHeight={itemHeight}
        items={sortData(columns, data, sortBy)}
        renderBufferItem={this.getBufferItem.bind(this, columns)}
        renderItem={this.getRowCells.bind(this, columns, sortBy, buildRowOptions, idAttribute)}
        scrollDelay={200}
        tagName="tbody" />
    );
  }

  render() {
    let {props, state} = this;
    let {columns, data, idAttribute} = props;
    let classes = classNames(props.className, 'flush-bottom');
    let sortBy = state.sortBy;
    let itemHeight = state.itemHeight || props.itemHeight || 0;

    return (

          {props.colGroup}

              {this.getHeaders(columns, sortBy)}

          {this.getTBody(columns, data, sortBy, itemHeight, idAttribute)}

    );
  }
}

Table.defaultProps = {
  buildRowOptions: () => { return {}; },
  sortBy: {},
  emptyMessage: 'No data'
};

Table.propTypes = {
  // Optional attributes to be passed to the row elements.
  buildRowOptions: PropTypes.func,

  className: PropTypes.string,

  // Optional colgroup component.
  colGroup: PropTypes.object,

  // Define how columns should be rendered and if they are sortable.
  columns: PropTypes.arrayOf(
    PropTypes.shape({
      attributes: React.PropTypes.object,
      className: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
      ]),
      defaultContent: PropTypes.string,
      // Column cached by default; disable with this setting.
      dontCache: PropTypes.bool,
      heading: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
      ]).isRequired,
      prop: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      sortable: PropTypes.bool,
      // Custom sorting function. If this function returns null,
      // it will fallback to default sorting.
      sortFunction: PropTypes.func
    })
  ).isRequired,

  // Optional selector to use as container.
  containerSelector: PropTypes.string,

  // Data to display in the table.
  // Make sure to clone the data, cannot be modified!
  data: PropTypes.array.isRequired,

  // Text to show if there is no data.
  emptyMessage: PropTypes.string,

  // Optional item height for the scroll table. If not provided, it will render
  // once to measure the height of the first child.
  // NB: Initial render will stop any ongoing animation, if this is not provided
  itemHeight: PropTypes.number,

  // Provide what attribute in the data make a row unique.
  idAttribute: PropTypes.string.isRequired,

  // Optional callback function when sorting is complete.
  onSortCallback: PropTypes.func,

  // Optional default sorting criteria.
  sortBy: PropTypes.shape({
    order: PropTypes.oneOf(['asc', 'desc']),
    prop: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
  })
};
<|endoftext|>


<commit_before>'use strict'

// Tractor Beam / Zumo
const DIR_R = 7
const DIR_L = 8
const PWM_R = 9
const PWM_L = 10

function success (message) {
  console.log(message)
}

function error (message) {
  console.error(message)
}

module.exports = class TractorBeam {
  connect () {
    firmata.connect(function () {
      console.log('connected')
    }, error)
  }

  setSpeed (dir, pwm, speed) {
    const FORWARD = firmata.LOW
    const REVERSE = firmata.HIGH

    let direction = FORWARD
    if (speed < 0) {
      speed = 255 + speed
      direction = REVERSE
    }
    firmata.digitalWrite(dir, direction, success, error)
    firmata.analogWrite(pwm, speed, success, error)
  }

  setLeftSpeed (speed) {
    this.setSpeed(DIR_L, PWM_L, speed)
  }

  setRightSpeed (speed) {
    this.setSpeed(DIR_R, PWM_R, speed)
  }

  setSpeeds (speeds) {
    this.setLeftSpeed(speeds[0])
    this.setRightSpeed(speeds[1])

    if (this._safetyTimeout) {
      clearTimeout(this._safetyTimeout)
      this._safetyTimeout = null
    }
    this._safetyTimeout = setTimeout(function () {
      this._safetyTimeout = null
      this.setLeftSpeed(0)
      this.setRightSpeed(0)
    }.bind(this), 500)
  }
}
<commit_msg>simplify timeout logic<commit_after>'use strict'

const TIMEOUT = 500 // ms

// Tractor Beam / Zumo
const DIR_R = 7
const DIR_L = 8
const PWM_R = 9
const PWM_L = 10

function success (message) {
  console.log(message)
}

function error (message) {
  console.error(message)
}

module.exports = class TractorBeam {
  connect () {
    firmata.connect(function () {
      console.log('connected')
    }, error)
  }

  setSpeed (dir, pwm, speed) {
    const FORWARD = firmata.LOW
    const REVERSE = firmata.HIGH

    let direction = FORWARD
    if (speed < 0) {
      speed = 255 + speed
      direction = REVERSE
    }
    firmata.digitalWrite(dir, direction, success, error)
    firmata.analogWrite(pwm, speed, success, error)
  }

  setLeftSpeed (speed) {
    this.setSpeed(DIR_L, PWM_L, speed)
  }

  setRightSpeed (speed) {
    this.setSpeed(DIR_R, PWM_R, speed)
  }

  setSpeeds (speeds) {
    this.setLeftSpeed(speeds[0])
    this.setRightSpeed(speeds[1])

    if (this._safetyTimeout) clearTimeout(this._safetyTimeout)
    this._safetyTimeout = setTimeout(function () {
      this.setLeftSpeed(0)
      this.setRightSpeed(0)
    }.bind(this), TIMEOUT)
  }
}
<|endoftext|>