fix: route APNs delivery per-token by registered environment
Issue: after defaulting APNs delivery to production (#2381), development builds installed from Xcode stopped receiving notifications entirely: their sandbox device tokens were sent to the production APNs endpoint, rejected as BadDeviceToken, and dropped as dead. Fix: the iOS shell reads the aps-environment entitlement from the embedded provisioning profile and exposes it to the web layer as a document-start user script (added in capacitorDidLoad, since Capacitor replaces the userContentController after webViewConfiguration(for:)). Token registration reports the environment to the server, which stores it per token and groups delivery by environment for both relay and direct APNs sends. OPENCHAMBER_APNS_ENVIRONMENT remains as an explicit override forcing every send to one environment. TestFlight/App Store builds and older clients without the field default to production, preserving released behavior; the relay already accepts env per send request.
This commit is contained in:
@@ -21,6 +21,19 @@ import { useUIStore } from '@/stores/useUIStore';
|
||||
// the Google Services Gradle plugin on Android), so @capacitor/push-notifications' register()
|
||||
// returns the right token per platform. The token is sent to the server tagged with its platform
|
||||
// so the relay routes it to APNs vs FCM.
|
||||
// APNs environment of this build. Xcode/dev-signed installs get sandbox device tokens,
|
||||
// TestFlight/App Store installs get production ones; the native iOS shell reports which via
|
||||
// a global injected in SceneDelegate (see packages/mobile/ios/App/App/AppDelegate.swift).
|
||||
// Undefined when the global is absent (Android, or a shell predating the injection) — the
|
||||
// server then defaults to production, matching released builds.
|
||||
const getApnsEnvironment = (): 'sandbox' | 'production' | undefined => {
|
||||
if (typeof window === 'undefined') return undefined;
|
||||
const env = (window as typeof window & { __OPENCHAMBER_APNS_ENV__?: string }).__OPENCHAMBER_APNS_ENV__;
|
||||
if (env === 'development') return 'sandbox';
|
||||
if (env === 'production') return 'production';
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const isNativePushPlatform = (): boolean => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
const capacitor = (window as typeof window & { Capacitor?: { getPlatform?: () => string } }).Capacitor;
|
||||
@@ -56,7 +69,11 @@ export const useNativePushRegistration = (options: { enabled: boolean }): void =
|
||||
const registrationHandle = await PushNotifications.addListener('registration', (token) => {
|
||||
lastTokenRef.current = token.value;
|
||||
const apis = getRegisteredRuntimeAPIs();
|
||||
void apis?.push?.registerApnsToken?.({ token: token.value, platform: getClientPlatform() });
|
||||
void apis?.push?.registerApnsToken?.({
|
||||
token: token.value,
|
||||
platform: getClientPlatform(),
|
||||
environment: getApnsEnvironment(),
|
||||
});
|
||||
});
|
||||
|
||||
const registrationErrorHandle = await PushNotifications.addListener('registrationError', (error) => {
|
||||
|
||||
@@ -778,6 +778,11 @@ export interface ApnsTokenPayload {
|
||||
token: string;
|
||||
/** 'ios' (APNs) or 'android' (FCM) — lets the relay route the token to the right service. */
|
||||
platform?: string;
|
||||
/**
|
||||
* APNs environment the token belongs to: 'sandbox' for Xcode/dev-signed installs,
|
||||
* 'production' for TestFlight/App Store. Omitted when unknown (server defaults to production).
|
||||
*/
|
||||
environment?: 'sandbox' | 'production';
|
||||
}
|
||||
|
||||
export interface PushAPI {
|
||||
|
||||
Reference in New Issue
Block a user