build: 444813b5-5daf-49b2-bf1a-7e4caeab4e17
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts';
|
||||
import { createClient } from 'npm:@supabase/supabase-js@2';
|
||||
import { cert, getApps, initializeApp } from 'npm:firebase-admin@12.2.0/app';
|
||||
import { getMessaging } from 'npm:firebase-admin@12.2.0/messaging';
|
||||
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers':
|
||||
'authorization, x-client-info, apikey, content-type',
|
||||
};
|
||||
|
||||
// Singleton — Edge Functions can be warm-reused; avoid "app already exists" error
|
||||
const app =
|
||||
getApps()[0] ??
|
||||
initializeApp({
|
||||
credential: cert(
|
||||
JSON.parse(
|
||||
atob(Deno.env.get('FIREBASE_SERVICE_ACCOUNT')!.replace(/\s/g, ''))
|
||||
)
|
||||
),
|
||||
});
|
||||
const messaging = getMessaging(app);
|
||||
|
||||
serve(async (req) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
||||
const supabaseAnonKey = Deno.env.get('SUPABASE_ANON_KEY')!;
|
||||
|
||||
const authHeader = req.headers.get('Authorization');
|
||||
if (!authHeader) {
|
||||
return new Response(JSON.stringify({ error: 'Missing auth headers' }), {
|
||||
status: 401,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
const supabase = createClient(supabaseUrl, supabaseAnonKey, {
|
||||
global: { headers: { Authorization: authHeader } },
|
||||
});
|
||||
|
||||
const body = await req.json();
|
||||
const {
|
||||
userId,
|
||||
title,
|
||||
body: messageBody,
|
||||
type,
|
||||
orderId,
|
||||
voucherId,
|
||||
priority = 'important',
|
||||
persist = true,
|
||||
delaySeconds,
|
||||
} = body;
|
||||
|
||||
if (!userId || !title || !messageBody || !type) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Missing required parameters' }),
|
||||
{
|
||||
status: 400,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (delaySeconds && delaySeconds > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1000));
|
||||
}
|
||||
|
||||
// Persist notification if needed (using service role key to insert securely if needed, but here auth is user)
|
||||
// Wait, if an admin/webhook is calling this, they will use service_role. If a user is calling, they use their token.
|
||||
// Let's create a service-role client just for DB updates to ensure it works even if sender is different from recipient
|
||||
const serviceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
||||
const adminSupabase = createClient(supabaseUrl, serviceRoleKey);
|
||||
|
||||
let notificationId: string | null = null;
|
||||
let persisted = false;
|
||||
|
||||
if (persist) {
|
||||
const { data: notifData, error: notifError } = await adminSupabase
|
||||
.from('notifications')
|
||||
.insert({
|
||||
user_id: userId,
|
||||
title,
|
||||
body: messageBody,
|
||||
type,
|
||||
priority,
|
||||
order_id: orderId || null,
|
||||
voucher_id: voucherId || null,
|
||||
})
|
||||
.select('id')
|
||||
.single();
|
||||
|
||||
if (!notifError && notifData) {
|
||||
notificationId = notifData.id;
|
||||
persisted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get user's profile to find fcm_token and preferences
|
||||
const { data: profile } = await adminSupabase
|
||||
.from('profiles')
|
||||
.select('fcm_token, notification_push_enabled')
|
||||
.eq('id', userId)
|
||||
.single();
|
||||
|
||||
let sent = false;
|
||||
|
||||
if (profile && profile.notification_push_enabled && profile.fcm_token) {
|
||||
const message = {
|
||||
notification: {
|
||||
title: title,
|
||||
body: messageBody,
|
||||
},
|
||||
data: {
|
||||
orderId: orderId || '',
|
||||
voucherId: voucherId || '',
|
||||
type: type || '',
|
||||
},
|
||||
apns: {
|
||||
payload: {
|
||||
aps: {
|
||||
badge: 1,
|
||||
sound: 'default',
|
||||
},
|
||||
},
|
||||
},
|
||||
android: {
|
||||
notification: {
|
||||
sound: 'default',
|
||||
notificationCount: 1,
|
||||
},
|
||||
},
|
||||
token: profile.fcm_token,
|
||||
};
|
||||
|
||||
try {
|
||||
await messaging.send(message);
|
||||
sent = true;
|
||||
} catch (err: any) {
|
||||
if (
|
||||
err.code === 'messaging/registration-token-not-registered' ||
|
||||
err.code === 'messaging/invalid-registration-token'
|
||||
) {
|
||||
// Token is stale — remove it from the DB
|
||||
await adminSupabase
|
||||
.from('profiles')
|
||||
.update({ fcm_token: null })
|
||||
.eq('id', userId);
|
||||
}
|
||||
console.error('FCM send error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ ok: true, sent, persisted, notificationId }),
|
||||
{
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
} catch (err: any) {
|
||||
return new Response(JSON.stringify({ error: err.message }), {
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user