import { ConfigPlugin, withAndroidManifest, withEntitlementsPlist, withXcodeProject, } from "expo/config-plugins"; import streamplaceReactNativeWebRTC from "../config-react-native-webrtc"; export const withNotificationsIOS: ConfigPlugin = (config) => { config = withEntitlementsPlist(config, (config) => { config.modResults["aps-environment"] = "production"; return config; }); return config; }; export const withoutNotificationsIOS: ConfigPlugin = (config) => { config = withEntitlementsPlist(config, (config) => { delete config.modResults["aps-environment"]; return config; }); return config; }; const withAndroidProfileable = (config) => { return withAndroidManifest(config, (config) => { const androidManifest = config.modResults.manifest; if ( !androidManifest.application || androidManifest.application.length === 0 ) { throw new Error("No application found in AndroidManifest.xml"); } const mainApplication = androidManifest.application[0]; (mainApplication as any).profileable = [ { $: { "android:shell": "true", "android:enabled": "true", }, }, ]; return config; }); }; const withConsistentVersionNumber = ( config, { version }: { version: string }, ) => { // if (!config.ios) { // config.ios = {}; // } // if (!config.ios.infoPlist) { // config.ios.infoPlist = {}; // } config = withXcodeProject(config, (config) => { for (let [k, v] of Object.entries( config.modResults.hash.project.objects.XCBuildConfiguration, )) { const obj = v as any; if (!obj.buildSettings) { continue; } if (typeof obj.buildSettings.MARKETING_VERSION !== "undefined") { obj.buildSettings.MARKETING_VERSION = version; } if (typeof obj.buildSettings.CURRENT_PROJECT_VERSION !== "undefined") { obj.buildSettings.CURRENT_PROJECT_VERSION = version; } } return config; }); return config; }; // turn a semver string into a always-increasing integer for google export const versionCode = (verStr: string) => { const [major, minor, patch] = verStr.split(".").map((x) => parseInt(x)); return major * 1000 * 1000 + minor * 1000 + patch; }; export default function () { const isProd = process.env["SP_PRODUCTION_RELEASE"] === "true" || !!process.env.CI; const enableSentry = process.env["SP_ENABLE_SENTRY"] === "true"; const pkg = require("./package.json"); const name = isProd ? "Streamplace" : "Devplace"; let bundle = isProd ? "tv.aquareum" : "tv.aquareum.dev"; if (process.env["SP_BUNDLE_OVERRIDE"]) { bundle = process.env["SP_BUNDLE_OVERRIDE"]; } let appleTeamId = process.env["SP_APPLE_TEAM_ID"]; const scheme = process.env["SP_APP_SCHEME"] ?? bundle; return { expo: { name: name, slug: name, version: pkg.version, // Only rev this to the current version when native dependencies change! runtimeVersion: pkg.runtimeVersion, orientation: "default", icon: "./assets/images/icon.png", scheme: scheme, userInterfaceStyle: "automatic", splash: { image: "./assets/images/splash.png", resizeMode: "contain", backgroundColor: "#ffffff", }, assetBundlePatterns: ["**/*"], ios: { supportsTablet: true, bundleIdentifier: bundle, infoPlist: { UIBackgroundModes: ["fetch", "remote-notification"], LSMinimumSystemVersion: "12.0", }, ...(appleTeamId ? { appleTeamId, } : {}), ...(isProd ? { googleServicesFile: "./GoogleService-Info.plist", entitlements: { "aps-environment": "production", }, associatedDomains: ["applinks:stream.place"], } : {}), }, android: { adaptiveIcon: { foregroundImage: "./assets/images/adaptive-icon.png", backgroundColor: "#ffffff", }, package: bundle, edgeToEdgeEnabled: true, versionCode: versionCode(pkg.version), intentFilters: [ { action: "VIEW", autoVerify: true, data: [ { scheme: "https", host: "stream.place", pathPattern: "/.*:.*", }, ], category: ["BROWSABLE", "DEFAULT"], }, { action: "VIEW", autoVerify: true, data: [ { scheme: "https", host: "stream.place", pathPattern: "/.*\\\\..*", }, ], category: ["BROWSABLE", "DEFAULT"], }, { action: "VIEW", autoVerify: true, data: [ { scheme: "https", host: "stream.place", path: "/", }, ], category: ["BROWSABLE", "DEFAULT"], }, ], ...(isProd ? { googleServicesFile: "./google-services.json", permissions: [ "android.permission.SCHEDULE_EXACT_ALARM", "android.permission.POST_NOTIFICATIONS", ], } : {}), }, web: { bundler: "metro", output: "single", favicon: "./assets/images/favicon.png", }, plugins: [ withAndroidProfileable, "expo-video", "expo-web-browser", "expo-screen-orientation", streamplaceReactNativeWebRTC, [ "expo-video", { supportsBackgroundPlayback: true, supportsPictureInPicture: true, }, ], ["expo-sqlite", { useSQLCipher: true }], "expo-file-system", [ "expo-font", { fonts: [ "./assets/fonts/AtkinsonHyperlegibleNext-Regular.ttf", "./assets/fonts/AtkinsonHyperlegibleNext-Light.ttf", "./assets/fonts/AtkinsonHyperlegibleNext-ExtraLight.ttf", "./assets/fonts/AtkinsonHyperlegibleNext-Medium.ttf", "./assets/fonts/AtkinsonHyperlegibleNext-SemiBold.ttf", "./assets/fonts/AtkinsonHyperlegibleNext-Bold.ttf", "./assets/fonts/AtkinsonHyperlegibleNext-ExtraBold.ttf", "./assets/fonts/AtkinsonHyperlegibleMono-Regular.ttf", "./assets/fonts/AtkinsonHyperlegibleMono-Medium.ttf", "./assets/fonts/AtkinsonHyperlegibleMono-SemiBold.ttf", "./assets/fonts/AtkinsonHyperlegibleMono-Bold.ttf", ], }, ], [ "expo-build-properties", { ios: { useFrameworks: "static", }, // uncomment to test OTA updates to http://localhost:8080 // android: { // usesCleartextTraffic: true, // }, }, ], [ "expo-asset", { assets: ["assets"], }, ], [withConsistentVersionNumber, { version: pkg.version }], [ "react-native-edge-to-edge", { android: { parentTheme: "Default", enforceNavigationBarContrast: false, }, }, ], ...(isProd ? [ "@react-native-firebase/app", "@react-native-firebase/messaging", [withNotificationsIOS, {}], ] : ["expo-dev-launcher", withoutNotificationsIOS]), ...(enableSentry ? [ [ "@sentry/react-native/expo", { url: "https://sentry.io/", project: process.env["SP_SENTRY_APP"] || "app", organization: process.env["SP_SENTRY_ORG"] || "streamplace", }, ], ] : []), ], experiments: { typedRoutes: true, }, updates: isProd ? { url: `https://stream.place/api/manifest`, enabled: true, checkAutomatically: "ON_LOAD", fallbackToCacheTimeout: 30000, codeSigningCertificate: "./code-signing/certs/certificate.pem", codeSigningMetadata: { keyid: "main", alg: "rsa-v1_5-sha256", }, } : {}, }, }; }