Облачные функции

Облачная функция выполняет серверный код в ответ на событие или запрос — своего сервера для этого не нужно. Так обрабатывают данные, вызывают внешние API, рассылают уведомления, запускают работу с ИИ и, что важнее всего, держат ключи и бизнес-логику подальше от клиента.

FlutterFlow поддерживает и Firebase Cloud Functions, и Supabase Edge Functions.

Firebase Cloud Functions

Firebase Cloud Functions выполняют код на Node.js по событиям Firebase и HTTPS-запросам: отправить письмо, обработать загруженный файл, сгенерировать содержимое, среагировать на изменение в базе.

Создавать, править, разворачивать и вызывать их можно прямо из FlutterFlow.

заметка

Подборка сценариев применения — в документации Firebase.

Как добавить

Разберём на примере: функция генерирует логотип по текстовому описанию.

Функция берёт текст из виджета TextField, вызывает API генерации изображений и возвращает ссылку на картинку — её показывает виджет Image.

инфо
Что нужно заранее

1. Переменные состояния страницы

Понадобятся две переменные состояния страницы:

  1. generatingImage (Boolean) — управляет показом индикатора загрузки: становится True перед вызовом и False после получения результата.
  2. logoImage (ImagePath) — сюда ложится ссылка на сгенерированную картинку.

img_6.png

2. Страница

Нужна страница с полем ввода. Быстрее всего взять шаблон или сгенерировать её; собрать с нуля — см. сборку раскладки.

img_7.png

Что здесь важно:

3. Создание и развёртывание

  1. Откройте Cloud Functions в меню навигации слева.
  2. Нажмите + Add — добавится newCloudFunction.
  3. Задайте Cloud Function Name.

Настройки заготовки

Справа настраивается следующее:

  1. Memory Allocation — сколько памяти отводится функции. Больше памяти — быстрее работа тяжёлых задач, но дороже каждый вызов.

  2. Timeout (s) — сколько секунд функции разрешено работать до принудительной остановки. Оплата идёт за время выполнения, поэтому большой таймаут стоит ставить осознанно.

  3. Require Authentication — вызывать функцию смогут только авторизованные пользователи.

  4. Cloud Function Region — где физически выполняется функция. Значение должно совпадать с Default GCP resource location и с регионом в расширенных настройках Firebase.

cf-region.avif

Вход и выход

  1. Чтобы функция что-то возвращала, включите Return Value и выберите тип. В примере это ImagePath — ссылка на сгенерированный логотип.

  2. Чтобы передать данные, нажмите + Add parameters: задайте имя, тип, отметьте Is List для списка и снимите Nullable, если значение обязательно. В примере это параметр prompt типа String.

  3. Собственные типы данных передаются в виде JSON: каждое поле типа — пара «ключ-значение». Для списка функция ждёт массив таких объектов. Скажем, для типа Person с полями Name и Age:

       // JSON:
       { "Name": "John", "Age": 30 }

       // Код функции:
       return {
         "name": person.name,
         "age": person.age
       };

Для списка:

        // JSON
         [ { "Name": "John", "Age": 30 }, { "Name": "Jane", "Age": 25 } ]

        // Код функции:
        return filteredpersons.map(filteredpersons => {
          return {
            "name": filteredpersons.name,
            "age": filteredpersons.age
          };
        });

Развёртывание

  1. Нажмите значок [</>] — откроется заготовка кода, затем </> Copy to Editor. Полезно сначала развернуть пустую заготовку и убедиться, что развёртывание вообще проходит.

  2. Допишите в редакторе код функции.

  3. Нажмите Save Cloud Function.

  4. Нажмите Deploy.

Код из примера:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const https = require('https');

exports.logoMaker = functions.region('us-central1')
    .runWith({
        timeoutSeconds: 10,
        memory: '512MB'
    }).https.onCall((data, context) => {
        return new Promise((resolve, reject) => {
            const prompt = data.prompt;
            if (!prompt) {
                reject(new functions.https.HttpsError('invalid-argument', 'No prompt provided'));
                return;
            }

            const postData = JSON.stringify({
                model: "dall-e-3",
                prompt: prompt,
                n: 1,
                size: "1024x1024"
            });

            const options = {
                hostname: 'api.openai.com',
                port: 443,
                path: '/v1/images/generations',
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer YOUR-APIKEY`,
                    'Content-Length': postData.length
                }
            };

            const req = https.request(options, (res) => {
                let responseBody = '';

                res.on('data', (chunk) => {
                    responseBody += chunk;
                });

                res.on('end', () => {
                    try {
                        const responseJSON = JSON.parse(responseBody);
                        if (responseJSON.data && responseJSON.data.length > 0) {
                            // Берём ссылку на первое изображение
                            const firstImageUrl = responseJSON.data[0].url;
                            resolve(firstImageUrl);
                        } else {
                            reject(new functions.https.HttpsError('not-found', 'No images found'));
                        }
                    } catch (error) {
                        reject(new functions.https.HttpsError('internal', 'Error processing response', error));
                    }
                });
            });

            req.on('error', (error) => {
                reject(new functions.https.HttpsError('internal', 'Error generating image', error));
            });

            req.write(postData);
            req.end();
        });
    });
совет
Важно

После любой правки настроек заготовки или параметров пересоздайте заготовку кода — или поправьте свой код руками. Иначе сигнатура функции разойдётся с тем, что вызывает приложение.

совет
Подключение пакета

Функции могут понадобиться сторонние библиотеки. Любой пакет npm подключается через файл package.json — в нём же лежат метаданные проекта, скрипты развёртывания и допустимые версии Node.js.

Впишите пакет в раздел dependencies.

img_9.png

4. Вызов функции

Созданная функция появляется в списке действий. В примере по нажатию кнопки сначала выставляется generatingImage = True, затем вызывается Cloud Function Action.



5. Использование результата

Чтобы работать с результатом, задайте при добавлении действия Action Output Variable Name — дальше значение доступно в Set from Variable > Action Outputs > [имя переменной].

В примере полученная ссылка на логотип записывается в переменную logoImage.

Проверка функции

Развёрнутую функцию можно вызвать прямо из консоли Google Cloud — не записывая ничего в Firestore и не запуская приложение.

Разберём на встроенной функции FlutterFlow sendUserPushNotificationsTrigger:

  1. Откройте адрес: https://console.cloud.google.com/functions/details/us-central1/sendUserPushNotificationsTrigger?env=gen1&project=<projectID>&tab=testing
    Здесь:

    • <projectID> замените на идентификатор своего проекта GCP или Firebase;
    • для другой функции замените sendUserPushNotificationsTrigger на её имя.
  2. Вставьте в поле Configure Triggering Event такой JSON:

    {
        "value": {
            "name": "projects/<projectID>/databases/(default)/documents/sendUserPushNotificationsTrigger/<documentID>",
            "fields": {
                "scheduled_time": { "stringValue": "" },
                "initial_page_name": { "stringValue": "" },
                "notification_title": { "stringValue": "Your friends are missing you!" },
                "notification_text": { "stringValue": "Please come back to Nanochat" },
                "user_refs": { "stringValue": "users/VXu6EvFMl5M8KMXriYRvFEWTFHA2" }
            }
        }
    }
  3. В свойстве name:

    • замените <projectID> на свой проект;
    • замените <documentID> на идентификатор документа — документ должен уже существовать в Firestore;
    • для другой функции подставьте её коллекцию вместо sendUserPushNotificationsTrigger.
  4. Заполните fields под своё сообщение. Приведённый набор полей относится к встроенной функции sendUserPushNotificationsTrigger; у вашей функции поля будут свои.

  5. Нажмите TEST THE FUNCTION.

Функция выполнится, а результат осядет в журналах Google Cloud Logging.

Частые вопросы

Почему развёртывание не проходит в только что созданном проекте?

Потому что в новом проекте Google Cloud ещё не включены нужные API и не выданы права.

  1. Откройте адрес: https://console.cloud.google.com/functions/list?referrer=search&hl=en&project=<projectID>, подставив свой идентификатор проекта.
  2. Нажмите Create Function — GCP предложит включить Cloud Build и Cloud Functions.
  3. После Next появится предложение включить Cloud Run Admin API. cloud-run-admin-api
  4. Дальше нужно выдать права служебному аккаунту сборки. На следующей странице разверните пример функции вроде helloHttp — появится запрос: You need to grant the following roles to the build service account to deploy a function: roles/cloudbuild.builds.builder to <projectID>-compute@developer.gserviceaccount.com.
  5. Нажмите Grant и разверните пример. После этого пример можно удалить.

Теперь функции из FlutterFlow будут разворачиваться нормально.

Ошибки при развёртывании функции

img_10.png

Разборы типичных ошибок собраны в обсуждении сообщества.

Ошибка CORS при вызове функции

В ответе не хватает заголовка **Access-Control-Allow-Origin**. Такое бывает с новыми функциями независимо от того, разворачивались они из FlutterFlow или нет.

Что делать:

  1. Откройте список функций своего проекта Google Cloud.
  2. Выберите нужную функцию.
  3. Перейдите на вкладку Permissions.
  4. Откройте VIEW BY ROLES.
  5. Убедитесь, что есть строка Cloud Functions Invoker с принципалом allUsers. Если её нет, нажмите Grant Access и добавьте allUsers с ролью Cloud Functions Invoker.

add-cf-invoker-role

Supabase Edge Functions

Supabase Edge Functions выполняют серверный код на Deno и TypeScript в инфраструктуре Supabase. Их берут для работы с ИИ, обёрток над внешними API, вебхуков, платежей и любой обработки на стороне сервера.

В отличие от функций Firebase, они тесно связаны с вашим проектом Supabase и выполняются ближе к пользователю.

инфо
Что нужно заранее

Проект должен быть подключён к Supabase — см. настройку Supabase.

Как добавить

Разберём на примере: функция составляет краткую сводку по отзывам о товаре.

Она принимает список отзывов, отправляет их модели и возвращает JSON со сводкой и общей тональностью. Здесь же виден главный смысл таких функций: ключ доступа к API лежит на стороне Supabase, а не в приложении.

1. Создание и развёртывание

  1. Откройте раздел Cloud Functions в меню навигации.

  2. Нажмите + и выберите Supabase Edge Function.

  3. Задайте имя — в примере это getAIReviewsSummary.

  4. Настройте функцию. В примере она принимает список отзывов и возвращает JSON со сводкой и тональностью.

    Что ещё настраивается:

    • Verify JWT — проверять токен из заголовка запроса. Выключите, чтобы разрешить вызовы без авторизации.
    • Enable CORS — добавлять заголовки CORS и обрабатывать предварительные запросы. Обязательно для вызова из веб-приложения.
    • Return Value — тип возвращаемого значения.
    • Define Parameters — параметры тела запроса.
  5. Нажмите кнопку генерации кода и скопируйте заготовку в редактор. Дальше заготовку можно отдать ИИ-помощнику вместе с описанием задачи, а полученный код проверить — сверить логику и формат ответа.

  6. Вставьте готовый код обратно в редактор.

  7. Нажмите Save Edge Function.

  8. Нажмите Deploy.

  9. FlutterFlow покажет список функций, доступных для развёртывания.

  10. Нажмите Deploy у нужной — она уйдёт в подключённый проект Supabase.

совет
Подключение пакета

Внешние пакеты Deno, npm и JSR подключаются на вкладке Dependencies (deno.json).

Например, для пакета lodash:

{
  "imports": {
    "lodash": "npm:lodash@4.17.21"
  }
}

После этого в коде функции:

import _ from "lodash"

add-packge-edge-function

2. Хранение ключей

Ключи и токены нельзя прописывать в коде функции. Для них есть отдельное место — секреты Supabase: там они хранятся зашифрованными и не попадают ни в приложение, ни в репозиторий.

Откройте проект Supabase, перейдите в Edge Functions > Secrets, введите имя и значение и нажмите Save:

ANTHROPIC_KEY=your-api-key

В функции значение читается через Deno.env.get():

const apiKey = Deno.env.get("ANTHROPIC_KEY")

if (!apiKey) {
  return new Response("Missing ANTHROPIC_KEY", {
    status: 500,
    headers: { ...corsHeaders, "Content-Type": "text/plain" },
  })
}

edge-functions-handle-secrets.avif

3. Вызов и результат

Развёрнутая функция вызывается действием Edge Function — по нажатию кнопки, при загрузке страницы или откуда угодно ещё. Передайте ей нужные параметры, в примере это список отзывов.

trigger-edge-function

Если функция возвращает значение, задайте Action Output Variable Name. Дальше результат доступен в Set from Variable > Action Outputs > [имя переменной].

В примере поля summary и sentiment из ответа записываются в переменные состояния и выводятся на экран.

use-edge-function-result

перевод официальной документации FlutterFlow

обновлено

ESC