Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
11 kB · 356 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357import { ConfigPlugin, withAndroidManifest, withDangerousMod, withEntitlementsPlist, withXcodeProject,} from "expo/config-plugins";import fs from "fs";import path from "path";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; });};
// For e2e builds only (SP_E2E_BUILD=true, `make android-e2e`): trust CAs in// the device's user store, where hack/e2e-local.sh installs the e2e harness's// throwaway CA so the app can reach its HTTPS station and PDS. Never ship// this — it lets whoever can add a user CA intercept the app's traffic.// Cleartext stays off, as in a release build (targetSdk 28+ default).const withE2EUserTrust: ConfigPlugin = (config) => { config = withDangerousMod(config, [ "android", async (config) => { const dir = path.join( config.modRequest.platformProjectRoot, "app/src/main/res/xml", ); fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync( path.join(dir, "e2e_network_security_config.xml"), `<?xml version="1.0" encoding="utf-8"?><network-security-config> <base-config cleartextTrafficPermitted="false"> <trust-anchors> <certificates src="system" /> <certificates src="user" /> </trust-anchors> </base-config></network-security-config>`, ); return config; }, ]); return withAndroidManifest(config, (config) => { const app = config.modResults.manifest.application?.[0]; if (!app) { throw new Error("No application found in AndroidManifest.xml"); } app.$["android:networkSecurityConfig"] = "@xml/e2e_network_security_config"; 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 googleexport 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 isE2E = process.env["SP_E2E_BUILD"] === "true"; const pkg = require("./package.json"); // Brand imagery and colors are generated from the active brand directory // (see brand/README.md); expo prebuild derives every native icon format // from the generated PNGs. let brand; try { brand = require("./assets/generated/brand.json"); } catch { throw new Error( "Missing generated brand assets — run `pnpm run brand` from the repo root.", ); } const name = isProd ? brand.name : "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/generated/icon.png", scheme: scheme, userInterfaceStyle: "automatic", splash: { image: "./assets/generated/splash.png", resizeMode: "contain", backgroundColor: brand.colors.splashBackground, }, 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/generated/adaptive-icon.png", backgroundColor: brand.colors.adaptiveIconBackground, }, 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/generated/favicon.png", }, plugins: [ withAndroidProfileable, "expo-video", "expo-image", "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/Geist-Regular.ttf", "./assets/fonts/Geist-Medium.ttf", "./assets/fonts/Geist-SemiBold.ttf",
"./assets/fonts/GeistMono-Regular.ttf", "./assets/fonts/GeistMono-Medium.ttf", "./assets/fonts/GeistMono-SemiBold.ttf", ], }, ], [ "expo-build-properties", { ios: { useFrameworks: "static", forceStaticLinking: ["RNFBApp", "RNFBMessaging"], // useful if you can't get forceStaticLinking to work //buildReactNativeFromSource: true, }, // uncomment to test OTA updates to http://localhost:8080 // android: { // usesCleartextTraffic: true, // }, android: { packagingOptions: { pickFirst: ["**/libcrypto.so"], }, }, }, ], [ "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", { url: "https://sentry.io/", project: process.env["SP_SENTRY_APP"] || "app", organization: process.env["SP_SENTRY_ORG"] || "streamplace", }, ], ] : []), ...(isE2E ? [withE2EUserTrust] : []), ], // e2e builds must run the JS they were built with, not whatever // production update the manifest serves updates: isProd && !isE2E ? { 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", }, } : {}, }, };}