@@ -5,6 +5,9 @@ import path from 'node:path';
55import type { ConfigContext , ExpoConfig } from 'expo/config' ;
66import { type ConfigPlugin , withEntitlementsPlist } from 'expo/config-plugins' ;
77
8+ // CommonJS, because Node loads what app.config.ts imports as it is.
9+ import { withAppIntents } from './plugins/with-app-intents' ;
10+
811
912// One config for Expo Go, the web export and the development build.
1013// DEBATELAB_BUILD=dev (set by the `development` profile in eas.json) adds the
@@ -22,9 +25,21 @@ type PermissionStrings = { speechRecognition: string; microphone: string };
2225
2326const build = process . env . DEBATELAB_BUILD ;
2427const appGroup = process . env . DEBATELAB_APP_GROUP ;
28+ // DEBATELAB_APPLE_ACCOUNT=1: the build is signed by DebateOwl's own Apple
29+ // Developer team, whose App IDs carry the capabilities in docs/apple-platform.md
30+ // (Sign in with Apple, Push, Time Sensitive Notifications, Associated Domains,
31+ // In-App Purchase). Without it a development build keeps to what the borrowed
32+ // signing profile allows.
33+ const appleAccount = process . env . DEBATELAB_APPLE_ACCOUNT ;
2534if ( build !== undefined && build !== '' && build !== 'dev' ) {
2635 throw new Error ( `DEBATELAB_BUILD must be "dev" or unset, not "${ build } ".` ) ;
2736}
37+ if ( appleAccount !== undefined && appleAccount !== '' && appleAccount !== '1' ) {
38+ throw new Error ( `DEBATELAB_APPLE_ACCOUNT must be "1" or unset, not "${ appleAccount } ".` ) ;
39+ }
40+ if ( appleAccount === '1' && build !== 'dev' ) {
41+ throw new Error ( 'DEBATELAB_APPLE_ACCOUNT only applies to a development build (DEBATELAB_BUILD=dev).' ) ;
42+ }
2843if ( appGroup && build !== 'dev' ) {
2944 throw new Error ( 'DEBATELAB_APP_GROUP only applies to a development build (DEBATELAB_BUILD=dev).' ) ;
3045}
@@ -54,11 +69,14 @@ function iosLocale(strings: PermissionStrings) {
5469
5570// expo-widgets adds the push entitlement even with its push option off, and
5671// prebuild applies expo-notifications' plugin on its own once the package is
57- // installed, which adds it too. The signing profile has no push, so every
58- // development build drops it until a profile with push exists.
59- const withoutPushEntitlement : ConfigPlugin = ( config ) =>
72+ // installed, which adds it too; expo-apple-authentication's plugin, also
73+ // applied on its own, adds Sign in with Apple. The borrowed signing profile
74+ // has neither, so a development build drops both unless it is signed by
75+ // DebateOwl's own account (DEBATELAB_APPLE_ACCOUNT).
76+ const withoutAccountEntitlements : ConfigPlugin = ( config ) =>
6077 withEntitlementsPlist ( config , ( mod ) => {
6178 delete mod . modResults [ 'aps-environment' ] ;
79+ delete mod . modResults [ 'com.apple.developer.applesignin' ] ;
6280 return mod ;
6381 } ) ;
6482
@@ -163,6 +181,40 @@ export function withRound4(config: ExpoConfig, opts: { appGroup?: string; projec
163181 return config ;
164182}
165183
184+ // Round 6 (docs/apple-platform.md): Sign in with Apple and Google, the
185+ // time-sensitive timer alert, Handoff and push, for development builds only,
186+ // so the Expo Go config never changes. What needs DebateOwl's own Apple
187+ // account waits for DEBATELAB_APPLE_ACCOUNT.
188+ export function withApplePlatform ( config : ExpoConfig , opts : { appleAccount : boolean } ) : ExpoConfig {
189+ // Handoff (src/lib/handoff.ts), next to Expo Router's own activity type.
190+ config . ios = {
191+ ...config . ios ,
192+ infoPlist : {
193+ ...config . ios ?. infoPlist ,
194+ NSUserActivityTypes : [ ...( ( config . ios ?. infoPlist ?. NSUserActivityTypes as string [ ] | undefined ) ?? [ ] ) , 'com.pistack.debateowl.browsing' ] ,
195+ } ,
196+ } ;
197+ config . extra = {
198+ ...config . extra ,
199+ // Google sign-in runs in any development build: the redirect comes back on the app's own scheme.
200+ googleSignIn : true ,
201+ signInWithApple : opts . appleAccount ,
202+ } ;
203+ if ( ! opts . appleAccount ) return config ;
204+ config . ios = { ...config . ios , usesAppleSignIn : true } ;
205+ config . ios . associatedDomains = [
206+ ...( config . ios . associatedDomains ?? [ ] ) ,
207+ // Handoff from debateowl.com in Safari to the app.
208+ 'activitycontinuation:debateowl.com' ,
209+ ] ;
210+ config . ios . entitlements = {
211+ ...config . ios . entitlements ,
212+ // The timer's time-up alert breaks through a Focus (src/lib/timer-alerts.ts).
213+ 'com.apple.developer.usernotifications.time-sensitive' : true ,
214+ } ;
215+ return config ;
216+ }
217+
166218export default ( { projectRoot } : ConfigContext ) : ExpoConfig => {
167219 const config : ExpoConfig = {
168220 name : 'DebateOwl' ,
@@ -278,7 +330,12 @@ export default ({ projectRoot }: ConfigContext): ExpoConfig => {
278330 }
279331 // The Current Debate widget (with the App Group), quick actions and Spotlight; see withRound4 above.
280332 withRound4 ( config , { appGroup, projectRoot } ) ;
333+ withApplePlatform ( config , { appleAccount : appleAccount === '1' } ) ;
334+ // Siri and Shortcuts: the intents compile into the app target (plugins/with-app-intents.js).
335+ const withIntents = withAppIntents ( config ) ;
336+ // DebateOwl's own account has Push and Sign in with Apple on its App ID, so its builds keep both.
337+ if ( appleAccount === '1' ) return withIntents ;
281338 // Registered before Expo applies the plugins list. Mods registered later act
282339 // first, so this one runs after the plugins have written their entitlements.
283- return withoutPushEntitlement ( config ) ;
340+ return withoutAccountEntitlements ( withIntents ) ;
284341} ;
0 commit comments