Přeskočit na obsah

React Native (@frontmail/react-native)

@frontmail/react-native umožní aplikaci pro iOS nebo Android odesílat e-maily přes vaše šablony s public key – bez vlastního backendu. Funguje s Expem i s čistým React Native.

Expo:

Terminál
npx expo install @frontmail/react-native @react-native-async-storage/async-storage react-native-webview

Čisté React Native:

Terminál
npm install @frontmail/react-native @react-native-async-storage/async-storage react-native-webview
npx pod-install

Oba další balíčky jsou volitelné peer závislosti:

Balíček K čemu je potřeba
@react-native-async-storage/async-storage limitRate, který přežije restart aplikace
react-native-webview <TurnstileWebView>

Povolte mobilní aplikace

Sekce “Povolte mobilní aplikace”

Nativní aplikace neposílají hlavičku Origin ani Referer, takže požadavky z aplikace (bez ohledu na to, jestli máte nastavené povolené weby) skončí chybou 403 origin_not_allowed, dokud v dashboardu nezapnete Zabezpečení → Mobilní aplikace → Povolit mobilní aplikace a nekliknete na Uložit. Viz Nastavení zabezpečení.

Požadavky z webů se dál kontrolují podle seznamu, takže přepínač ochranu vašich webových formulářů neoslabí. Přepínač je potřeba i s prázdným seznamem.

Obalte aplikaci do FrontmailProvider a odesílejte pomocí useSendEmail. Objekt s volbami udržujte stabilní – definujte ho na úrovni modulu nebo pomocí useMemo:

import { useState } from 'react';
import { Button, Text, TextInput, View } from 'react-native';
import { FrontmailProvider, useSendEmail } from '@frontmail/react-native';
const frontmailOptions = { publicKey: 'pk_4f2a…', limitRate: { id: 'contact', throttle: 30_000 } };
export default function App() {
return (
<FrontmailProvider options={frontmailOptions}>
<ContactScreen />
</FrontmailProvider>
);
}
function ContactScreen() {
const { send, status, error } = useSendEmail('svc_01J9…', 'tpl_contact');
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
return (
<View>
<TextInput value={email} onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" />
<TextInput value={message} onChangeText={setMessage} multiline />
<Button title="Odeslat" disabled={status === 'sending'} onPress={() => send({ email, message })} />
{status === 'sent' && <Text>Děkujeme!</Text>}
{status === 'held' && <Text>Přijato – brzy ji doručíme.</Text>}
{error && <Text>{error.message}</Text>}
</View>
);
}

options přijímá publicKey, apiUrl, retry, limitRate, storageProvider a blockList. privateKey nepřijímá. Případně můžete předat existujícího klienta: <FrontmailProvider client={client}>.

useSendEmail(serviceId, templateId) vrací { send, getStatus, status, error, result, reset }. status přechází idle → sending → sent | held | error. send(params, options?) se resolvne s výsledkem, nebo při chybě s undefined (chyba je v error) – nikdy nevyhodí výjimku, takže try/catch nepotřebujete. reset() vrátí hook do stavu idle.

Každý požadavek nese hlavičku X-Frontmail-Client: @frontmail/react-native/<verze>.

Pokud šablona vyžaduje Turnstile, vykreslete <TurnstileWebView> a token předejte do send. Site key nepotřebujete: komponenta ve výchozím stavu používá sdílený mobilní klíč Frontmailu. Načte ho z GET /v1/public-config API nastaveného v provideru (s cache, takže jen jednou) a widget Cloudflare zobrazí v react-native-webview jako vloženou HTML stránku s adresou https://mobile.frontmail.dev – z té adresy se nic nestahuje.

import { useRef, useState } from 'react';
import { Button } from 'react-native';
import { TurnstileWebView, useSendEmail } from '@frontmail/react-native';
import type { TurnstileWebViewHandle } from '@frontmail/react-native';
function ContactScreen() {
const { send, status } = useSendEmail('svc_01J9…', 'tpl_contact');
const turnstile = useRef<TurnstileWebViewHandle>(null);
const [token, setToken] = useState<string>();
const onSubmit = async () => {
await send({ email, message }, { turnstileToken: token });
// Token jde použít jen jednou – pro další pokus si vyžádejte nový.
setToken(undefined);
turnstile.current?.reset();
};
return (
<>
{/* …pole formuláře… */}
<TurnstileWebView
ref={turnstile}
onToken={setToken}
onExpire={() => setToken(undefined)}
onError={(e) => console.warn(e.message)}
/>
<Button title="Odeslat" disabled={!token || status === 'sending'} onPress={onSubmit} />
</>
);
}

Props: onToken a volitelně siteKey, baseUrl, onError, onExpire, theme (auto | light | dark), size (normal | compact | flexible), action, language, style a webViewProps (předají se WebView).

WebView je uzamčené na widget: načte jen vloženou stránku (baseUrl) a výzvu Cloudflaru, odkazy ve widgetu se otevřou v systémovém prohlížeči a do vašich callbacků se dostanou jen zprávy ze stránky widgetu. webViewProps nemohou přepsat source, originWhitelist, hlídání navigace, onMessage ani nastavení JavaScriptu a přístupu k souborům.

Požadavky z aplikace nemají hlavičku Origin, takže jejich tokeny Frontmail ověří svým sdíleným mobilním tajným klíčem. Vlastní klíče Turnstile k tomu nepotřebujete – se zapnutým Povolit mobilní aplikace můžete CAPTCHA u šablon aplikace zapnout, i když organizace klíče zatím nemá.

Pokud chcete místo toho použít vlastní widget Turnstile, předejte siteKey i baseUrl:

<TurnstileWebView
ref={turnstile}
siteKey="YOUR_TURNSTILE_SITE_KEY"
baseUrl="https://example.com"
onToken={setToken}
/>

SDK pak tokeny z tohoto widgetu automaticky posílá s turnstile_key: "org" a Frontmail je ověří secret key ze Zabezpečení → Ochrana proti botům (Turnstile).

Token platí asi 5 minut a jde použít jen jednou: po každém odeslání, úspěšném i neúspěšném, zavolejte ref.current.reset().

limitRate a úložiště

Sekce “limitRate a úložiště”

limitRate omezuje odesílání přímo v zařízení, třeba na nejvýš jednu zprávu za 30 sekund. V React Native neexistuje location.pathname, takže vždy nastavte limitRate.id:

import AsyncStorage from '@react-native-async-storage/async-storage';
import { asyncStorageProvider } from '@frontmail/react-native';
const frontmailOptions = {
publicKey: 'pk_4f2a…',
limitRate: { id: 'contact', throttle: 30_000 },
storageProvider: asyncStorageProvider(AsyncStorage),
};

Bez storageProvider použije SDK AsyncStorage, pokud je nainstalovaný; jinak si limit drží jen v paměti (po restartu aplikace se vynuluje) a při vývoji vypíše varování. Paměťové úložiště zvolíte výslovně pomocí memoryStorageProvider().

limitRate chrání jen před nechtěným opakovaným ťuknutím – skutečná ochrana je na serveru (viz Zabezpečení).

getStatus() z hooku zjistí stav doručení poslední přijaté zprávy. Pro všechno ostatní vrací useFrontmail() klienta z provideru (send, getStatus):

import { useFrontmail } from '@frontmail/react-native';
const client = useFrontmail();
const status = await client.getStatus(messageId, { token });

Opakování a idempotence

Sekce “Opakování a idempotence”

Chyby sítě, 5xx a 429 se automaticky opakují a všechny pokusy jednoho odeslání sdílejí stejný idempotency key, takže opakování e-mail nikdy nepošle dvakrát. Klíče jsou UUID v4 – SDK používá crypto.randomUUID a na Hermesu, který ho nemá, přejde na vlastní generátor. Viz Retry a idempotence.

S vygenerovanými typy (npx frontmail types rozšíří @frontmail/react-native) typově kontroluje useSendEmail('svc_…', 'tpl_contact').send({...}) parametry dané šablony.

error je FrontmailError s vlastnostmi code, status a message. Jak reagovat na jednotlivé kódy, popisuje Zpracování chyb.

  • Do aplikace patří jen public key (pk_…). Cokoli v balíčku aplikace se dá vytáhnout, takže private key v ní je jako zveřejněný. Viz Public key a private key.
  • Se zapnutým Povolit mobilní aplikace přijmeme požadavky bez hlavičky Origin odkudkoli – poslat je může jakýkoli skript, který zná váš public key. Spolehněte se na limit na IP adresu, Turnstile u všech šablon, které aplikace používá, a na seznam blokovaných hodnot.

Ukázkový projekt: examples/react-native.