Ошибки FCM при использовании Firebase admin sdk и облачных функций

Получение этой ошибки неоднократно. Ошибка №1

    { Error: fcm.googleapis.com network timeout. Please try again.
    at FirebaseAppError.Error (native)
    at FirebaseAppError.FirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:25:28)
    at new FirebaseAppError (/user_code/node_modules/firebase-admin/lib/utils/error.js:70:23)
    at TLSSocket.<anonymous> (/user_code/node_modules/firebase-admin/lib/utils/api-request.js:106:51)
    at emitNone (events.js:86:13)
    at TLSSocket.emit (events.js:185:7)
    at TLSSocket.Socket._onTimeout (net.js:339:8)
    at ontimeout (timers.js:365:14)
    at tryOnTimeout (timers.js:237:5)
    at Timer.listOnTimeout (timers.js:207:5)
  errorInfo: 
   { code: 'app/network-timeout',
     message: 'fcm.googleapis.com network timeout. Please try again.' } }

Еще одна ошибка, которую я получаю несколько раз. Ошибка №2

 { Error: Credential implementation provided to initializeApp() via the "credential" property failed to fetch a valid Google OAuth2 access token with the following error: "read ECONNRESET".
    at FirebaseAppError.Error (native)
    at FirebaseAppError.FirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:25:28)
    at new FirebaseAppError (/user_code/node_modules/firebase-admin/lib/utils/error.js:70:23)
    at /user_code/node_modules/firebase-admin/lib/firebase-app.js:106:23
    at process._tickDomainCallback (internal/process/next_tick.js:129:7)
  errorInfo: 
   { code: 'app/invalid-credential',
     message: 'Credential implementation provided to initializeApp() via the "credential" property failed to fetch a valid Google OAuth2 access token with the following error: "read ECONNRESET".' } }

Еще один тип. Ошибка №3

Error sending message: { Error: A network request error has occurred: read ECONNRESET
    at FirebaseAppError.Error (native)
    at FirebaseAppError.FirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:25:28)
    at new FirebaseAppError (/user_code/node_modules/firebase-admin/lib/utils/error.js:70:23)
    at ClientRequest.<anonymous> (/user_code/node_modules/firebase-admin/lib/utils/api-request.js:115:43)
    at emitOne (events.js:96:13)
    at ClientRequest.emit (events.js:188:7)
    at TLSSocket.socketErrorListener (_http_client.js:310:9)
    at emitOne (events.js:96:13)
    at TLSSocket.emit (events.js:188:7)
    at emitErrorNT (net.js:1276:8)
  errorInfo: 
   { code: 'app/network-error',
     message: 'A network request error has occurred: read ECONNRESET' } }

То, что я пытаюсь сделать с помощью своих облачных функций, — это проверить данные пользователей на основе сопоставления цен и отправки сообщений FCM. Облачная функция — это триггер базы данных. Вот код моей облачной функции. Я не вижу никаких проблем в коде, так как я использую промисы.

// Checks price alerts for users
exports.priceAlertCheck = functions.database.ref('/crons/alerts/price').onWrite(event => {
  const promises = [];
  admin.database().ref(`/alerts/price`).once('value', function(alertSnapshot) {
    alertSnapshot.forEach(function(dataSnapshot) {
      promises.push(createPriceAlertPromise(dataSnapshot));
    });
  });
  return Promise.all(promises);
});

function createPriceUrl(fromCurrency, toCurrency, exchange) {
  return 'https://zzzz.com/data/price?fsym='
          +fromCurrency+'&tsyms='+toCurrency+(exchange ? '&e='+exchange : '');
}

function createPriceAlertPromise(snapshot) {
  const comboKeyArray = snapshot.key.split('-');
  const fromCurrency = comboKeyArray[0];
  const toCurrency = comboKeyArray[1];
  const exchange = comboKeyArray[2];
  return request(createPriceUrl(fromCurrency, toCurrency, exchange), function (error, response, body) {
      if (!error && response.statusCode == 200) {
        const jsonobj = JSON.parse(response.body);
        const currentPrice = jsonobj[toCurrency];
        const promises = [];

        snapshot.forEach(function(data) {
            promises.push(sendAlertNotifications(snapshot.key, data.key, currentPrice));
        });
        return Promise.all(promises);
      } else {
        console.log('Error fetching price', snapshot.key);
      }
  });
}

function sendAlertNotifications(comboKey, userId, currentPrice) {
  const getUserPromise = admin.database()
                          .ref(`/users/${userId}`)
                          .once('value');
  const getUserPriceAlertsPromise = admin.database()
                          .ref(`/user_alerts/price/${userId}`)
                          .once('value');
  return Promise.all([getUserPromise, getUserPriceAlertsPromise]).then(results => {
    const userSnapshot = results[0];
    if(!userSnapshot.val()){
      return console.log('Not user details', userId)
    }
    const instanceId = userSnapshot.val().instanceId;
    const subscriptionStatus = userSnapshot.val().subscriptionStatus;
    const priceAlertSnapshot = results[1];
    if(subscriptionStatus != 1){
      return console.log('Not Sending alerts. Subscription Expired', userId);
    }
    // Check if there are any device tokens.
    if (!priceAlertSnapshot.hasChildren()) {
      return console.log('There are no alerts to send for', comboKey, ", userId:", userId);
    }
    console.log("Alerts of users fetched for ", comboKey, " : ", priceAlertSnapshot.numChildren(), ", userId:", userId);
    const promises = [];
    priceAlertSnapshot.forEach(function(dataSnapshot) {
        promises.push(sendAlertNotification(userId, instanceId, currentPrice, dataSnapshot));
    });
    return Promise.all(promises);
  })
  .catch(error => {
    console.log("Error getting user alert details:", error, ", userId:", userId);
  });
}

function sendAlertNotification(userId, instanceId, currentPrice, dataSnapshot) {
  const comboKey = dataSnapshot.val().name;
  const comboKeyArray = comboKey.split('-');
  const fromCurrency = comboKeyArray[0];
  const toCurrency = comboKeyArray[1];
  const exchange = comboKeyArray[2];
  const alertPrice = dataSnapshot.val().value;
  if(priceAlertConditionCheck(currentPrice, dataSnapshot)) {
    // Notification details.
    const payload = {
      notification: {
        title: `${fromCurrency} Price Alert`,
        body: "You have been notified",
        sound: 'default',
        tag: comboKey
      },
      data: {
        title: `${fromCurrency} Price Alert`,
        body: "You have been notified",
        name: comboKey,
        sound: 'default',
        type: "alert"
      }
    };
    // Set the message as high priority and have it expire after 24 hours.
    var options = {
      priority: "high",
      timeToLive: 60 * 10
    };

    return admin.messaging().sendToDevice(instanceId, payload, options).then(response => {
      response.results.forEach((result, index) => {
        const error = result.error;
        if (error) {
          console.error("Failure sending message:", error, " userId:", userId, " token:", instanceId);
        }
        console.log("Successfully sent message:", response, ", userId:", userId);
      });
    })
    .catch(error => {
      console.log("Error sending message:", error, " userId:", userId, " token:", instanceId);
    });
  }
  return;
}

В настоящее время данных мало, и все же я получаю 30% сбоев (от 10 до 15 записей) в базе данных firebase. Как это будет работать, когда есть 10 тысяч записей? Как я могу предотвратить эти ошибки? Также нет документации для этих кодов ошибок «приложение/», а только для ошибок «сообщение/».

ОБНОВЛЕНИЕ №1: обновленная функция:

function createPriceAlertPromise(snapshot) {
  const comboKeyArray = snapshot.key.split('-');
  const fromCurrency = comboKeyArray[0];
  const toCurrency = comboKeyArray[1];
  const exchange = comboKeyArray[2];
  return rp(createPriceUrl(fromCurrency, toCurrency, exchange),
  {resolveWithFullResponse: true}).then(response => {
    if (response.statusCode === 200) {
      const jsonobj = JSON.parse(response.body);
      const currentPrice = jsonobj[toCurrency];
      const promises = [];

      snapshot.forEach(function(data) {
          promises.push(sendAlertNotifications(snapshot.key, data.key, currentPrice));
      });
      return Promise.all(promises);
    }
    throw response.body;
  }).catch(error => {
    console.log('Error fetching price', error);
  });
}

ОБНОВЛЕНИЕ № 2: увеличено время ожидания функции до 540 секунд, но по-прежнему возникает ошибка № 1.

ОБНОВЛЕНИЕ № 3: обновленные функции: ошибка № 1 исчезла, но ошибка № 3 все еще существует и возникает чаще.

// Checks price alerts for users
exports.priceAlertCheck = functions.database.ref('/crons/alerts/price').onWrite(event => {
  return admin.database().ref(`/alerts/price`).once('value').then(alertSnapshot => {
    const promises = [];
    alertSnapshot.forEach(function(dataSnapshot) {
      promises.push(createPriceAlertPromise(dataSnapshot));
    });
    return Promise.all(promises).then(response => {
      return deleteFirebaseApp();
    })
    .catch(function(error) {
      return logError(error);
    });
  });
});
function createPriceAlertPromise(snapshot) {
  const comboKeyArray = snapshot.key.split('-');
  const fromCurrency = comboKeyArray[0];
  const toCurrency = comboKeyArray[1];
  const exchange = comboKeyArray[2];
  return rp(createPriceUrl(fromCurrency, toCurrency, exchange),
  {resolveWithFullResponse: true}).then(response => {
    if (response.statusCode === 200) {
      const jsonobj = JSON.parse(response.body);
      const currentPrice = jsonobj[toCurrency];

      const forEachPromise =  new Promise(function(resolve) {
        const promises = [];
        snapshot.forEach(function(data) {
          promises.push(sendAlertNotifications(snapshot.key, data.key, currentPrice));
        });
        resolve(promises);
      });

      forEachPromise.then(promises => {
        return Promise.all(promises);
      })
      .catch(error => {
        return reportError(error, { type: 'database_query', context: 'forEach'});
      });
    } else {
      throw response.body;
    }
  }).catch(error => {
    return reportError(error, { type: 'http_request', context: 'price fetching'});
  });
}

comment
Какие модули вы импортируете? Что возвращает request() (похоже, вы предполагаете, что он возвращает обещание)? Если вы импортируете модуль с именем request, чтобы получить доступ к этому методу запроса, он не возвращает промис. Это означает, что Cloud Functions, вероятно, выполняет очистку до того, как ваш запрос будет завершен, и разрывает соединение.   -  person Doug Stevenson    schedule 10.07.2017
comment
const functions = require('firebase-функции'); const admin = require('firebase-admin'); admin.initializeApp(functions.config().firebase); const cors = require('cors')({origin: true}); постоянный запрос = требуется ('запрос'); константный экспресс = требуется ('экспресс');   -  person 1HaKr    schedule 10.07.2017
comment
это модули, которые я импортирую. Да, я предполагал, что запрос возвращает обещание. У меня есть несколько обещаний базы данных и обещание HTTP-запроса, объединенных вместе. Что я должен использовать?   -  person 1HaKr    schedule 10.07.2017
comment
Посмотрите на модуль request-promise, который заключает запрос в обещание: github.com/request/request-promise   -  person Doug Stevenson    schedule 10.07.2017
comment
в конце какой функции вам пришлось вызывать admin.delete()? @1HaKr   -  person anho    schedule 17.10.2017
comment
Да, вызовите эту исправленную ошибку № 1   -  person 1HaKr    schedule 18.10.2017


Ответы (3)


В вашем коде используется модуль узла request, который не работает с промисами. При работе с облачными функциями обычно проще использовать оболочку вокруг этого модуля с именем request-promise. который возвращает обещание, чтобы вы могли реагировать на результаты HTTP-запроса более удобным способом для кода, работающего в Cloud Functions.

Как только вы начнете возвращать действительные промисы из своей функции, облачные функции будут ждать полного завершения этих запросов перед очисткой. Ошибка ECONNRESET является признаком того, что она очищается слишком рано, прежде чем ваши запросы будут выполнены. Я немного писал об этом в Firebase. сообщение в блоге недавно.

person Doug Stevenson    schedule 10.07.2017
comment
Эй, Дуг, я внес изменения, и теперь я использую модуль запроса-обещания. Теперь я не получаю ошибку № 2 и ошибку № 3, но я все еще получаю ошибку № 1. Я обновил вопрос с новым кодом - person 1HaKr; 10.07.2017
comment
Я не знаю об этом. Похоже, что FCM просто не отвечает. Я бы передал это в службу поддержки Firebase, если вы все еще видите это. firebase.google.com/support/contact/troubleshooting - person Doug Stevenson; 10.07.2017
comment
Да, я уже связался со службой поддержки Firebase, ожидая от них решения. - person 1HaKr; 10.07.2017
comment
эй, @1HaKr, ты получил ответ от службы поддержки firebase? или знаете, что вызывает эту ошибку? - person David Velásquez; 18.09.2017
comment
Да, они сказали мне добавить admin.delete() в конце функции в качестве обходного пути. - person 1HaKr; 22.09.2017
comment
получается, что я забыл вернуть промис из самой первой функции. Как только я верну это обещание chainsssss. Нет больше ошибки тайм-аута! Спасибо за это. - person peteroid; 09.10.2017

Строка 494: response.results.forEach((результат, индекс) => {

Следует изменить на:

вернуть RSVP.all(response.results.map((результат, индекс) => {

"forEach" возвращает значение null, что означает, что промис сразу разрешается с нулевым значением. «map» возвращает массив, а RSVP.all гарантирует, что все промисы в массиве разрешены.

В настоящее время функция уничтожается до того, как будут выполнены все асинхронные задачи, поэтому вы получаете сообщение об ошибке.

person laurenzlong    schedule 03.08.2017

Я «решил» проблему, обновив:

npm install -g firebase-tools@latest

npm install --save firebase-functions@latest firebase-admin@latest firebase@latest
person Harendra Kr. Jadon    schedule 05.04.2018