fix(ios): unblock iOS Debug builds + regenerate FRB for file transfer API (#41)

* fix(ios): preserve Silero VAD symbols in Flutter Debug builds

Flutter Debug builds split user code into a sibling Chanora.debug.dylib
alongside a minimal launcher executable; the six @_cdecl symbols
chanora_silero_vad_* land in the dylib, not the main binary. Two
problems were hiding behind that:

1. Debug.xcconfig only carried -exported_symbol flags, missing the
   load-bearing -u force-undefined flags Release.xcconfig already had.
   Without -u, the linker dropped the Swift @_cdecl symbols (no Swift
   caller exists) before -exported_symbol could re-export them, so the
   dylib shipped without the VAD entry points.

2. verify_silero_exports.sh inspected only ${EXECUTABLE_PATH}, which in
   Debug builds is the launcher stub. Even with the symbols correctly
   landing in the debug dylib, the script falsely reported them missing.

Mirror Release.xcconfig's -u + -exported_symbol pair in Debug.xcconfig
and teach the verify script to prefer Chanora.debug.dylib when it sits
next to the launcher. Release builds are unaffected (single fat binary
remains the inspected target).

* build(flutter): regenerate FRB bindings for file transfer API

Run flutter_rust_bridge_codegen against the post-PR-#40 chanora_bridge
to emit typed Dart bindings for initCache, downloadAvatar, downloadIcon,
clearFileCache, and fileCacheSize. Replaces the dynamic-cast +
NoSuchMethodError-swallowing shims that lib/src/rust/api.dart shipped
as a pre-regen safety net.

Functional change: downloadAvatar/downloadIcon now return Uint8List?
(was List<int>?) directly through the typed dispatch table. Existing
consumers stay source-compatible because Uint8List extends List<int>;
no call sites needed updates.

* fix(ios): set ITSAppUsesNonExemptEncryption to false

App uses only standard/exempt encryption (HTTPS, system-provided crypto);
declaring exempt status removes the App Store export-compliance prompt
at every TestFlight/release upload.
This commit is contained in:
Edison Jwa
2026-06-10 15:20:59 +09:00
committed by GitHub
parent aa796d7395
commit 0a6ef55937
8 changed files with 547 additions and 127 deletions
+253 -47
View File
@@ -38,7 +38,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
default_rust_auto_opaque = RustAutoOpaqueMoi,
);
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -20394775;
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 635684021;
// Section: executor
@@ -186,6 +186,41 @@ fn wire__crate__api__bridge_init_impl(
},
)
}
fn wire__crate__api__clear_file_cache_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
data_len_: i32,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "clear_file_cache",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let message = unsafe {
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
ptr_,
rust_vec_len_,
data_len_,
)
};
let mut deserializer =
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
deserializer.end();
move |context| async move {
transform_result_sse::<_, crate::BridgeError>(
(move || async move {
let output_ok = crate::api::clear_file_cache().await?;
Ok(output_ok)
})()
.await,
)
}
},
)
}
fn wire__crate__api__client_profile_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
@@ -332,6 +367,80 @@ fn wire__crate__api__disconnect_impl(
},
)
}
fn wire__crate__api__download_avatar_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
data_len_: i32,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "download_avatar",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let message = unsafe {
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
ptr_,
rust_vec_len_,
data_len_,
)
};
let mut deserializer =
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let api_avatar_hash = <String>::sse_decode(&mut deserializer);
let api_client_uid = <String>::sse_decode(&mut deserializer);
deserializer.end();
move |context| async move {
transform_result_sse::<_, crate::BridgeError>(
(move || async move {
let output_ok =
crate::api::download_avatar(api_avatar_hash, api_client_uid).await?;
Ok(output_ok)
})()
.await,
)
}
},
)
}
fn wire__crate__api__download_icon_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
data_len_: i32,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "download_icon",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let message = unsafe {
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
ptr_,
rust_vec_len_,
data_len_,
)
};
let mut deserializer =
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let api_icon_id = <u64>::sse_decode(&mut deserializer);
deserializer.end();
move |context| async move {
transform_result_sse::<_, crate::BridgeError>(
(move || async move {
let output_ok = crate::api::download_icon(api_icon_id).await?;
Ok(output_ok)
})()
.await,
)
}
},
)
}
fn wire__crate__api__enable_audio_debug_wav_dump_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
@@ -434,6 +543,41 @@ fn wire__crate__api__export_diagnostics_impl(
},
)
}
fn wire__crate__api__file_cache_size_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
data_len_: i32,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "file_cache_size",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let message = unsafe {
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
ptr_,
rust_vec_len_,
data_len_,
)
};
let mut deserializer =
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
deserializer.end();
move |context| async move {
transform_result_sse::<_, crate::BridgeError>(
(move || async move {
let output_ok = crate::api::file_cache_size().await?;
Ok(output_ok)
})()
.await,
)
}
},
)
}
fn wire__crate__api__get_audio_processing_config_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
@@ -702,6 +846,42 @@ fn wire__crate__api__handle_route_change_impl(
},
)
}
fn wire__crate__api__init_cache_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
data_len_: i32,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "init_cache",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let message = unsafe {
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
ptr_,
rust_vec_len_,
data_len_,
)
};
let mut deserializer =
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let api_dir = <String>::sse_decode(&mut deserializer);
deserializer.end();
move |context| async move {
transform_result_sse::<_, crate::BridgeError>(
(move || async move {
let output_ok = crate::api::init_cache(api_dir).await?;
Ok(output_ok)
})()
.await,
)
}
},
)
}
fn wire__crate__api__init_storage_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
@@ -2763,6 +2943,17 @@ impl SseDecode for Option<u64> {
}
}
impl SseDecode for Option<Vec<u8>> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
if (<bool>::sse_decode(deserializer)) {
return Some(<Vec<u8>>::sse_decode(deserializer));
} else {
return None;
}
}
}
impl SseDecode for crate::api::PermissionStateKind {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -2816,45 +3007,50 @@ fn pde_ffi_dispatcher_primary_impl(
2 => wire__crate__api__audio_processing_stats_impl(port, ptr, rust_vec_len, data_len),
3 => wire__crate__api__audio_stats_impl(port, ptr, rust_vec_len, data_len),
4 => wire__crate__api__bridge_init_impl(port, ptr, rust_vec_len, data_len),
5 => wire__crate__api__client_profile_impl(port, ptr, rust_vec_len, data_len),
6 => wire__crate__api__connect_impl(port, ptr, rust_vec_len, data_len),
7 => wire__crate__api__delete_bookmark_impl(port, ptr, rust_vec_len, data_len),
8 => wire__crate__api__disconnect_impl(port, ptr, rust_vec_len, data_len),
9 => wire__crate__api__enable_audio_debug_wav_dump_impl(port, ptr, rust_vec_len, data_len),
10 => wire__crate__api__events_stream_impl(port, ptr, rust_vec_len, data_len),
12 => wire__crate__api__get_audio_processing_config_impl(port, ptr, rust_vec_len, data_len),
13 => wire__crate__api__get_ptt_binding_impl(port, ptr, rust_vec_len, data_len),
14 => wire__crate__api__get_release_tail_ms_impl(port, ptr, rust_vec_len, data_len),
15 => wire__crate__api__get_transmit_mode_impl(port, ptr, rust_vec_len, data_len),
20 => wire__crate__api__init_storage_impl(port, ptr, rust_vec_len, data_len),
21 => wire__crate__api__input_level_stream_impl(port, ptr, rust_vec_len, data_len),
22 => wire__crate__api__is_connected_impl(port, ptr, rust_vec_len, data_len),
23 => wire__crate__api__list_audio_devices_impl(port, ptr, rust_vec_len, data_len),
24 => wire__crate__api__list_bookmarks_impl(port, ptr, rust_vec_len, data_len),
26 => wire__crate__api__move_to_channel_impl(port, ptr, rust_vec_len, data_len),
27 => wire__crate__api__prefetch_server_impl(port, ptr, rust_vec_len, data_len),
28 => wire__crate__api__ptt_descriptor_impl(port, ptr, rust_vec_len, data_len),
30 => wire__crate__api__send_chat_message_impl(port, ptr, rust_vec_len, data_len),
32 => wire__crate__api__set_audio_processing_config_impl(port, ptr, rust_vec_len, data_len),
33 => wire__crate__api__set_client_volume_impl(port, ptr, rust_vec_len, data_len),
34 => wire__crate__api__set_hard_mute_impl(port, ptr, rust_vec_len, data_len),
35 => wire__crate__api__set_input_device_impl(port, ptr, rust_vec_len, data_len),
36 => wire__crate__api__set_input_muted_impl(port, ptr, rust_vec_len, data_len),
37 => {
5 => wire__crate__api__clear_file_cache_impl(port, ptr, rust_vec_len, data_len),
6 => wire__crate__api__client_profile_impl(port, ptr, rust_vec_len, data_len),
7 => wire__crate__api__connect_impl(port, ptr, rust_vec_len, data_len),
8 => wire__crate__api__delete_bookmark_impl(port, ptr, rust_vec_len, data_len),
9 => wire__crate__api__disconnect_impl(port, ptr, rust_vec_len, data_len),
10 => wire__crate__api__download_avatar_impl(port, ptr, rust_vec_len, data_len),
11 => wire__crate__api__download_icon_impl(port, ptr, rust_vec_len, data_len),
12 => wire__crate__api__enable_audio_debug_wav_dump_impl(port, ptr, rust_vec_len, data_len),
13 => wire__crate__api__events_stream_impl(port, ptr, rust_vec_len, data_len),
15 => wire__crate__api__file_cache_size_impl(port, ptr, rust_vec_len, data_len),
16 => wire__crate__api__get_audio_processing_config_impl(port, ptr, rust_vec_len, data_len),
17 => wire__crate__api__get_ptt_binding_impl(port, ptr, rust_vec_len, data_len),
18 => wire__crate__api__get_release_tail_ms_impl(port, ptr, rust_vec_len, data_len),
19 => wire__crate__api__get_transmit_mode_impl(port, ptr, rust_vec_len, data_len),
24 => wire__crate__api__init_cache_impl(port, ptr, rust_vec_len, data_len),
25 => wire__crate__api__init_storage_impl(port, ptr, rust_vec_len, data_len),
26 => wire__crate__api__input_level_stream_impl(port, ptr, rust_vec_len, data_len),
27 => wire__crate__api__is_connected_impl(port, ptr, rust_vec_len, data_len),
28 => wire__crate__api__list_audio_devices_impl(port, ptr, rust_vec_len, data_len),
29 => wire__crate__api__list_bookmarks_impl(port, ptr, rust_vec_len, data_len),
31 => wire__crate__api__move_to_channel_impl(port, ptr, rust_vec_len, data_len),
32 => wire__crate__api__prefetch_server_impl(port, ptr, rust_vec_len, data_len),
33 => wire__crate__api__ptt_descriptor_impl(port, ptr, rust_vec_len, data_len),
35 => wire__crate__api__send_chat_message_impl(port, ptr, rust_vec_len, data_len),
37 => wire__crate__api__set_audio_processing_config_impl(port, ptr, rust_vec_len, data_len),
38 => wire__crate__api__set_client_volume_impl(port, ptr, rust_vec_len, data_len),
39 => wire__crate__api__set_hard_mute_impl(port, ptr, rust_vec_len, data_len),
40 => wire__crate__api__set_input_device_impl(port, ptr, rust_vec_len, data_len),
41 => wire__crate__api__set_input_muted_impl(port, ptr, rust_vec_len, data_len),
42 => {
wire__crate__api__set_ios_voice_processing_mode_impl(port, ptr, rust_vec_len, data_len)
}
39 => wire__crate__api__set_output_device_impl(port, ptr, rust_vec_len, data_len),
40 => wire__crate__api__set_output_gain_impl(port, ptr, rust_vec_len, data_len),
41 => wire__crate__api__set_output_muted_impl(port, ptr, rust_vec_len, data_len),
42 => wire__crate__api__set_ptt_impl(port, ptr, rust_vec_len, data_len),
43 => wire__crate__api__set_ptt_binding_impl(port, ptr, rust_vec_len, data_len),
44 => wire__crate__api__set_release_tail_ms_impl(port, ptr, rust_vec_len, data_len),
45 => wire__crate__api__set_transmit_mode_impl(port, ptr, rust_vec_len, data_len),
46 => wire__crate__api__set_vad_model_path_impl(port, ptr, rust_vec_len, data_len),
47 => wire__crate__api__snapshot_impl(port, ptr, rust_vec_len, data_len),
48 => wire__crate__api__update_bookmark_impl(port, ptr, rust_vec_len, data_len),
49 => wire__crate__api__voice_join_impl(port, ptr, rust_vec_len, data_len),
50 => wire__crate__api__voice_leave_impl(port, ptr, rust_vec_len, data_len),
44 => wire__crate__api__set_output_device_impl(port, ptr, rust_vec_len, data_len),
45 => wire__crate__api__set_output_gain_impl(port, ptr, rust_vec_len, data_len),
46 => wire__crate__api__set_output_muted_impl(port, ptr, rust_vec_len, data_len),
47 => wire__crate__api__set_ptt_impl(port, ptr, rust_vec_len, data_len),
48 => wire__crate__api__set_ptt_binding_impl(port, ptr, rust_vec_len, data_len),
49 => wire__crate__api__set_release_tail_ms_impl(port, ptr, rust_vec_len, data_len),
50 => wire__crate__api__set_transmit_mode_impl(port, ptr, rust_vec_len, data_len),
51 => wire__crate__api__set_vad_model_path_impl(port, ptr, rust_vec_len, data_len),
52 => wire__crate__api__snapshot_impl(port, ptr, rust_vec_len, data_len),
53 => wire__crate__api__update_bookmark_impl(port, ptr, rust_vec_len, data_len),
54 => wire__crate__api__voice_join_impl(port, ptr, rust_vec_len, data_len),
55 => wire__crate__api__voice_leave_impl(port, ptr, rust_vec_len, data_len),
_ => unreachable!(),
}
}
@@ -2867,19 +3063,19 @@ fn pde_ffi_dispatcher_sync_impl(
) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
// Codec=Pde (Serialization + dispatch), see doc to use other codecs
match func_id {
11 => wire__crate__api__export_diagnostics_impl(ptr, rust_vec_len, data_len),
16 => wire__crate__api__handle_interruption_began_impl(ptr, rust_vec_len, data_len),
17 => wire__crate__api__handle_interruption_ended_impl(ptr, rust_vec_len, data_len),
18 => wire__crate__api__handle_media_services_reset_with_route_impl(
14 => wire__crate__api__export_diagnostics_impl(ptr, rust_vec_len, data_len),
20 => wire__crate__api__handle_interruption_began_impl(ptr, rust_vec_len, data_len),
21 => wire__crate__api__handle_interruption_ended_impl(ptr, rust_vec_len, data_len),
22 => wire__crate__api__handle_media_services_reset_with_route_impl(
ptr,
rust_vec_len,
data_len,
),
19 => wire__crate__api__handle_route_change_impl(ptr, rust_vec_len, data_len),
25 => wire__crate__api__log_file_path_str_impl(ptr, rust_vec_len, data_len),
29 => wire__crate__api__record_lifecycle_event_impl(ptr, rust_vec_len, data_len),
31 => wire__crate__api__set_audio_output_route_impl(ptr, rust_vec_len, data_len),
38 => wire__crate__api__set_network_state_impl(ptr, rust_vec_len, data_len),
23 => wire__crate__api__handle_route_change_impl(ptr, rust_vec_len, data_len),
30 => wire__crate__api__log_file_path_str_impl(ptr, rust_vec_len, data_len),
34 => wire__crate__api__record_lifecycle_event_impl(ptr, rust_vec_len, data_len),
36 => wire__crate__api__set_audio_output_route_impl(ptr, rust_vec_len, data_len),
43 => wire__crate__api__set_network_state_impl(ptr, rust_vec_len, data_len),
_ => unreachable!(),
}
}
@@ -4556,6 +4752,16 @@ impl SseEncode for Option<u64> {
}
}
impl SseEncode for Option<Vec<u8>> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<bool>::sse_encode(self.is_some(), serializer);
if let Some(value) = self {
<Vec<u8>>::sse_encode(value, serializer);
}
}
}
impl SseEncode for crate::api::PermissionStateKind {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {