UNPKG

react-native-email-link

Version:
384 lines (350 loc) 8.82 kB
import { ActionSheetIOS, Linking } from "react-native"; import { EmailException } from "./email-exception"; const prefixes = { "apple-mail": "message://", gmail: "googlegmail://", inbox: "inbox-gmail://", spark: "readdle-spark://", airmail: "airmail://", outlook: "ms-outlook://", yahoo: "ymail://", superhuman: "superhuman://", yandex: "yandexmail://", fastmail: "fastmail://", protonmail: "protonmail://", seznamemail: "szn-email://", }; const titles = { "apple-mail": "Mail", gmail: "Gmail", inbox: "Inbox", spark: "Spark", airmail: "Airmail", outlook: "Outlook", yahoo: "Yahoo Mail", superhuman: "Superhuman", yandex: "Yandex", fastmail: "Fastmail", protonmail: "ProtonMail", seznamemail: "Email.cz", }; /** * Allowed params for each app url * - apple-mail: https://ios.gadgethacks.com/news/always-updated-list-ios-app-url-scheme-names-0184033/ * - gmail: https://stackoverflow.com/questions/32114455/open-gmail-app-from-my-app * - inbox: https://stackoverflow.com/questions/29655978/is-there-an-ios-mail-scheme-url-for-googles-inbox * - spark: https://helpspot.readdle.com/spark/index.php?pg=kb.page&id=791 * - airmail: https://help.airmailapp.com/en-us/article/airmail-ios-url-scheme-1q060gy/ * - outlook: https://stackoverflow.com/questions/32369198/i-just-want-to-open-ms-outlook-app-and-see-mailto-screen-using-url-scheme-at-ios * - fastmail: https://github.com/vtourraine/ThirdPartyMailer/blob/1.8.0/Sources/ThirdPartyMailer/ThirdPartyMailClient.swift#L80 */ const uriParams = { "apple-mail": { cc: "cc", bcc: "bcc", subject: "subject", body: "body", }, gmail: { path: "co", to: "to", cc: "cc", bcc: "bcc", subject: "subject", body: "body", }, inbox: { path: "co", to: "to", cc: "cc", bcc: "bcc", subject: "subject", body: "body", }, spark: { path: "compose", to: "recipient", cc: "cc", bcc: "bcc", subject: "subject", body: "body", }, airmail: { path: "compose", to: "to", cc: "cc", bcc: "bcc", subject: "subject", body: "htmlBody", }, outlook: { path: "compose", to: "to", cc: "cc", bcc: "bcc", subject: "subject", body: "body", }, yahoo: { path: "mail/compose", to: "to", cc: "cc", bcc: "bcc", subject: "subject", body: "body", }, superhuman: { path: "compose", to: "to", cc: "cc", bcc: "bcc", subject: "subject", body: "body", }, yandex: { path: "compose", }, fastmail: { path: "mail/compose", to: "to", cc: "cc", bcc: "bcc", subject: "subject", body: "body", }, protonmail: { path: "compose", }, seznamemail: { path: "mail", }, }; /** * Returns param to open app compose screen and pre-fill 'to', 'subject' and 'body', * @param {string} app * @param {{ * to: string, * cc: string, * bcc: string, * subject: string, * body: string, * }} options */ function getUrlParams(app, options) { const appParms = uriParams[app]; if (!appParms) { return ""; } const path = app === "apple-mail" ? options.to || "" : appParms.path; const urlParams = Object.keys(appParms).reduce((params, currentParam) => { if (options[currentParam]) { params.push(`${appParms[currentParam]}=${options[currentParam]}`); } return params; }, []); return `${path}?${urlParams.join("&")}`; } /** * Check if a given mail app is installed. * * @param {string} app * @returns {Promise<boolean>} */ export function isAppInstalled(app) { return new Promise((resolve) => { if (!(app in prefixes)) { return resolve(false); } Linking.canOpenURL(prefixes[app]) .then((isSupported) => { resolve(isSupported); }) .catch(() => resolve(false)); }); } /** * Ask the user to choose one of the available mail apps. * @param title * @param message * @param cancelLabel * @param removeText * @param defaultEmailLabel * @returns {Promise<String|null>} */ export function askAppChoice( title = "Open mail app", message = "Which app would you like to open?", cancelLabel = "Cancel", removeText = false, defaultEmailLabel = "Default email reader", actionType = "open", ) { return new Promise(async (resolve, reject) => { let availableApps = []; for (let app in prefixes) { let avail = await isAppInstalled(app); if (avail) { availableApps.push(app); } } if (!availableApps.length) { return reject(new EmailException("No email apps available")); } if (availableApps.length === 1) { return resolve(availableApps[0]); } let options = availableApps.map((app) => actionType === "compose" && app === "apple-mail" ? defaultEmailLabel : titles[app], ); options.push(cancelLabel); ActionSheetIOS.showActionSheetWithOptions( { options: options, cancelButtonIndex: options.length - 1, ...(removeText ? {} : { title, message }), }, (buttonIndex) => { if (buttonIndex === options.length - 1) { return resolve(null); } return resolve(availableApps[buttonIndex]); }, ); }); } /** * Returns the name of the app provided in the options object or the app selected by the user. * @param {{ * app: string | undefined | null, * }} options * @param {("open" | "compose")} actionType * @return string */ async function getApp(options, actionType) { if (options && typeof options !== "object") { throw new EmailException("First parameter must be an object of options."); } if ( "app" in options && options.app && Object.keys(prefixes).indexOf(options.app) < 0 ) { throw new EmailException( 'Option `app` should be undefined, null, or one of the following: "' + Object.keys(prefixes).join('", "') + '".', ); } let { app = null } = options; if (!app) { const { title, message, cancelLabel, removeText, defaultEmailLabel } = options; app = await askAppChoice( title, message, cancelLabel, removeText, defaultEmailLabel, actionType, ); } return app; } /** * Get available email clients * * @returns {Promise<{ * androidPackageName: string; * title: string; * prefix: string; * iOSAppName: string; * id: string; * }[]>} */ export function getEmailClients() { return new Promise(async (resolve, reject) => { let availableApps = []; for (let app in prefixes) { let avail = await isAppInstalled(app); if (avail) { availableApps.push(app); } } if (availableApps.length === 0) { return reject(new EmailException("No email apps available")); } const apps = availableApps.reduce((acc, app) => { const title = titles[app] || ""; if (title) { acc.push({ androidPackageName: "", // Android only title, prefix: prefixes[app], // iOS only iOSAppName: app, // iOS only id: app, }); return acc; } return acc; }, []); return resolve(apps); }); } /** * Open an email app, or let the user choose what app to open. * * @param {{ * app: string | undefined | null, * title: string, * message: string, * cancelLabel: string, * removeText: boolean * defaultEmailLabel: string * }} options */ export async function openInbox(options = {}) { const app = await getApp(options, "open"); if (!app) { return null; } await Linking.openURL(prefixes[app]); return { app, title: titles[app] }; } /** * Open an email app on the compose screen, or let the user choose what app to open on the compose screen. * You can pass `id` to open a specific app, or `null` to let the user choose. (`id` can be retrieved with `getEmailClients` * * @param {{ * app: string | undefined | null, * title: string, * message: string, * cancelLabel: string, * removeText: boolean, * defaultEmailLabel: string, * to: string, * cc: string, * bcc: string, * subject: string, * body: string, * encodeBody: boolean * }} options */ export async function openComposer(options) { const app = await getApp(options, "compose"); if (!app) { return null; } if (options.encodeBody) { options.body = encodeURIComponent(options.body); } const params = getUrlParams(app, options); let prefix = prefixes[app]; if (app === "apple-mail") { // apple mail prefix to compose an email is mailto prefix = "mailto:"; } await Linking.openURL(`${prefix}${params}`); return { app, title: titles[app] }; }