我正在创建一个 Typescript React Native 应用程序(使用 Expo),并尝试构建一个“邀请用户功能”,您可以在其中输入昵称和电子邮件,然后该用户将收到一封邀请他们加入该应用程序的电子邮件。
它的工作原理是为邀请和邮件创建 Firestore 集合
然后将它们从 Firestore 推送到 Trigger Email,然后通过 SMTP 推送到 Mailgun。
已在 Firestore 中成功创建邀请和邮件,但我在 Google 控制台上收到超时错误,并且 Mailgun 中未显示任何内容。
这是我的控制台日志:
INFO 2024-04-23T18:51:39.332357Z [resource.labels.functionName: ext-firestore-send-email-processQueue] Initializing extension with configuration
DEBUG 2024-04-23T18:51:39.402786533Z [resource.labels.functionName: ext-firestore-send-email-processQueue] [labels.executionId: y8j24zp92do1] Function execution started
INFO 2024-04-23T18:51:39.645436Z [resource.labels.functionName: ext-firestore-send-email-processQueue] [labels.executionId: y8j24zp92do1] Started execution of extension with configuration
INFO 2024-04-23T18:51:43.879766Z [resource.labels.functionName: ext-firestore-send-email-processQueue] [labels.executionId: y8j24zp92do1] Completed execution of extension
DEBUG 2024-04-23T18:51:43.883830637Z [resource.labels.functionName: ext-firestore-send-email-processQueue] [labels.executionId: y8j24zp92do1] Function execution took 4481 ms, finished with status: 'ok'
DEBUG 2024-04-23T18:52:35.972814575Z [resource.labels.functionName: ext-firestore-send-email-processQueue] [labels.executionId: yfsqgcwx6njq] Function execution took 59999 ms, finished with status: 'timeout'
INFO 2024-04-23T18:52:46.694433Z [resource.labels.functionName: ext-firestore-send-email-processQueue] Initializing extension with configuration
扩展超时错误:
{
insertId: "15ci506ffa9z6x"
labels: {2}
logName: "projects/icecreamsync/logs/cloudfunctions.googleapis.com%2Fcloud-functions"
receiveTimestamp: "2024-04-23T18:52:35.983830631Z"
resource: {2}
severity: "DEBUG"
textPayload: "Function execution took 59999 ms, finished with status: 'timeout'"
timestamp: "2024-04-23T18:52:35.972814575Z"
trace: "projects/icecreamsync/traces/651c7307ad3a2528ac08440baec5accb"
}
这是负责的代码:
// index.ts
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import { NodeMailgun } from "ts-mailgun";
admin.initializeApp();
// Mailgun instance
const mailer = new NodeMailgun();
mailer.apiKey = "api";
mailer.domain = "sandboxcxxxxx.mailgun.org";
mailer.fromEmail = "[email protected]";
mailer.fromTitle = "App Name";
mailer.init();
// Cloud Function to send an email
export const sendEmailOnCreate = functions.runWith({
timeoutSeconds: 120, // Set higher timeout
memory: '256MB' // Set adequate memory
}).firestore.document("mail/{mailId}").onCreate(async (snap, context) => {
const mailData = snap.data();
if (!mailData.to || !mailData.subject || !mailData.html) {
console.error("Required email fields are missing", mailData);
return null; // terminate function if essential fields are missing
}
try {
await mailer.send(mailData.to, mailData.subject, mailData.html);
console.log("Mail sent successfully");
return null;
} catch (error) {
console.error("Failed to send email:", error);
return null; // consider logging this error to a persistent store
}
});
//FirebaseService.ts
import { db } from '../firebaseConfig';
import { collection, addDoc, serverTimestamp } from 'firebase/firestore';
export async function inviteFriend(friendNickname: string, friendEmail: string, userId: string): Promise<string> {
const invitation = {
nickname: friendNickname,
email: friendEmail,
invitationSent: true, // Set to true assuming email will be sent
status: 'pending',
invitedBy: userId,
createdAt: serverTimestamp()
};
try {
const docRef = await addDoc(collection(db, "invitations"), invitation);
console.log("Invitation created with ID: ", docRef.id);
// Create a document in the mail collection expected by the Trigger Email extension
const emailContent = {
to: friendEmail,
message: {
subject: "You've been invited!",
html: `<p>Hello ${friendNickname},</p><p>You have been invited by ${userId}. Click here to accept the invitation.</p>`
}
};
await addDoc(collection(db, "mail"), emailContent);
return docRef.id;
} catch (error) {
console.error("Error creating invitation and sending email:", error);
throw error;
}
}
// SettingsScreen.tsx
import React, { useState } from 'react';
import { View, TextInput, Button, Text, Alert, StyleSheet } from 'react-native';
import { inviteFriend } from '../services/firebaseService';
const SettingsScreen: React.FC = () => {
const [email, setEmail] = useState<string>('');
const [nickname, setNickname] = useState<string>('');
const handleInvite = async () => {
const userId = "user123"; // This should ideally be fetched from your auth state
try {
// Pass UserID here
const invitationId = await inviteFriend(nickname, email, userId);
Alert.alert("Success", `Invitation sent successfully! ID: ${invitationId}`);
} catch (error) {
console.error("Failed to send invitation:", error);
Alert.alert("Error", "Failed to send invitation.");
}
};
return (
<View style={styles.container}>
<Text>Invite a Friend</Text>
<TextInput
style={styles.input}
placeholder="Nickname"
value={nickname}
onChangeText={setNickname}
/>
<TextInput
style={styles.input}
placeholder="Email"
value={email}
onChangeText={setEmail}
/>
<Button title="Invite" onPress={handleInvite} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 20
},
input: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
marginBottom: 10,
padding: 10
}
});
export default SettingsScreen;
问题是我将 SMTP 和 API 结合起来。我还需要重新初始化我的 firestore 功能。我还完全删除了扩展程序。
这是最终起作用的代码:
import * as functions from 'firebase-functions';
import { NodeMailgun } from 'ts-mailgun';
// Mailgun Configuration
const mailer = new NodeMailgun();
mailer.apiKey = '';
mailer.domain = '.org';
mailer.fromEmail = '';
mailer.fromTitle = '';
mailer.init();
// Firestore Trigger for Invitation
exports.sendInvitationEmail = functions.firestore
.document('invitations/{invitationId}')
.onCreate(async (snap, context) => {
const data = snap.data();
const email = data.email;
const nickname = data.nickname;
try {
await mailer.send(email, 'You are invited!', `<h1>Hello ${nickname}, you have been invited!</h1>`);
console.log('Invitation sent to', email);
} catch (error) {
console.error('Mail sending error:', error);
throw new functions.https.HttpsError('unknown', 'Failed to send invitation');
}
});
和
import React, { useState } from 'react';
import { View, TextInput, Button, StyleSheet, Text } from 'react-native';
import { db } from '../firebaseConfig'; //
import { collection, addDoc } from 'firebase/firestore';
export default function SettingsScreen() {
const [email, setEmail] = useState<string>('');
const [nickname, setNickname] = useState<string>('');
async function handleInvite() {
try {
await addDoc(collection(db, 'invitations'), {
email: email,
nickname: nickname,
createdAt: new Date(),
invitationSent: false,
status: 'pending'
});
alert('Invitation sent!');
} catch (error) {
console.error(error);
alert('Failed to send invitation.');
}
}
return (
<View style={styles.container}>
<Text>Email:</Text>
<TextInput style={styles.input} value={email} onChangeText={setEmail} placeholder="Enter email" />
<Text>Nickname:</Text>
<TextInput style={styles.input} value={nickname} onChangeText={setNickname} placeholder="Enter nickname" />
<Button title="Send Invitation" onPress={handleInvite} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 20,
},
input: {
height: 40,
marginBottom: 12,
borderWidth: 1,
padding: 10,
},
});