😇

Edge Functions FCMでハマった!

2024/04/29に公開

🤔やってみたいこと

Supabase公式の動画を参考に、Flutterアプリに通知を送るのをやってみたができなかった?
https://www.youtube.com/watch?v=CiSv9E6ZKVc&t=936s

注意点:
この記事は、Supabaseの使い方に慣れている人を対象にしているので省いている部分があります。

  1. auth
  2. CRUD

見過ごしていたことがあった!

  1. WebHookの設定をしていなかった💦
  2. BearerをBEARERと大文字で書いていた💦

🚀やってみたこと

fcm_tokenが保存されて、 idが存在すれば、Edget Functionsを実行できるようになんと機能を実装できた💦

Denoのコードは、Firebaseから新しい秘密鍵をダウンロードしてきて設定する必要がある。

ファイル名を修正して配置する。

import { createClient } from "npm:@supabase/supabase-js@2.39.3"
import { JWT } from 'npm:google-auth-library@9'

interface Notification {
  id: string
  fcm_token: string
}

interface WebhookPayload {
  type: 'INSERT'
  table: string
  record: Notification
  schema: 'public',
  fcm_record: null | Notification
}

const supabase = createClient(
  Deno.env.get('SUPABASE_URL') ?? '',
  Deno.env.get('SUPABASE_ANON_KEY') ?? '',
);

Deno.serve(async (req) => {
  const payload: WebhookPayload = await req.json()

  const { data } = await supabase
  .from('notification')
  .select('fcm_token')
  .eq('id', payload.record.id)
  .single()

  const fcmToken = data!.fcm_token as string

   const { default: serviceAccount } = await import('./service-account.json', {
    with: { type: 'json'},
   })

  const accessToken = await getAccessToken({
    clientEmail: serviceAccount.client_email,
    privateKey: serviceAccount.private_key,
  })

  console.log(`アクセストークン🍪: ${accessToken}`);
  
  const res = await fetch(
    `https://fcm.googleapis.com/v1/projects/${serviceAccount.project_id}/messages:send`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${accessToken}`,
      },
      body: JSON.stringify({
        message: {
          token: fcmToken,
          notification: {
            title: `Supabaseからの通知🔔`,
            body: `${payload.record.id} さんからの通知: ${payload.record.fcm_token}`
          },
        },
      }),
    }
  )
  console.log(`ステータス: ${res.status}`);
  console.log(`ステータステキスト: ${res.statusText}`);

  const resData = await res.json()
  if (res.status < 200 || 299 < res.status) {
    throw resData
  }

  return new Response(JSON.stringify(resData), {
    headers: { 'Content-Type': 'application/json' },
  })
})

const getAccessToken = ({
  clientEmail,
  privateKey,
}: {
  clientEmail: string
  privateKey: string
}): Promise<string> => {
  return new Promise((resolve, reject) => {
    const jwtClient = new JWT({
      email: clientEmail,
      key: privateKey,
      scopes: ['https://www.googleapis.com/auth/firebase.messaging'],
    })
    jwtClient.authorize((err, tokens) => {
      if (err) {
        reject(err)
        return
      }
      resolve(tokens!.access_token!)
    })
  })

}

通知の送信と受け取る実験ができるコードが書かれているファイル。ログインして、uuidを取得してあるので注意が必要です。

// Flutter imports:
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

class NotificationPage extends StatefulWidget {
  const NotificationPage({super.key});

  
  State<NotificationPage> createState() => _NotificationPageState();
}

class _NotificationPageState extends State<NotificationPage> {
  
  void initState() {
    super.initState();
    Supabase.instance.client.auth.onAuthStateChange.listen((event) async {
      if (event == AuthChangeEvent.signedIn) {
        await FirebaseMessaging.instance.requestPermission();

        await FirebaseMessaging.instance.getAPNSToken();
        final fcmToken = await FirebaseMessaging.instance.getToken();
        if (fcmToken != null) {
          await _setFcmToken(fcmToken);
        }
      } else if (event == AuthChangeEvent.signedOut) {}
    });
    FirebaseMessaging.instance.onTokenRefresh.listen((event) async {
      await _setFcmToken(event);
    });

    FirebaseMessaging.onMessage.listen((payload) {
      final notification = payload.notification;
      if (notification != null) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('${notification.title} ${notification.body}'),
          ),
        );
      }
    });
  }

  Future<void> _setFcmToken(String fcmToken) async {
    final userId = Supabase.instance.client.auth.currentUser?.id;
    if (userId != null) {
      await Supabase.instance.client.from('notification').upsert({
        'id': userId,
        'fcm_token': fcmToken,
      });
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: const Icon(Icons.notifications),
            onPressed: () async {
              final fcmToken = await FirebaseMessaging.instance.getToken();
              if (fcmToken != null) {
                await _setFcmToken(fcmToken);
              }
            },
          ),
        ],
        title: const Text('通知 Test'),
      ),
    );
  }
}

🙂最後に

今回は、1週間以上苦しめられた通知機能の実装をやってみました。 Auth0、トークン、正しいURLへのリクエストが必要なので、やること単純だけど、専門的な知識を求められましたね😅
HTTP, 認証・認可がわかればあとは、 logを見ながらエラーを解決できました。

Discussion