firebase_rs_sdk/storage/
api.rs

1use std::sync::{Arc, LazyLock};
2
3use crate::app::FirebaseApp;
4use crate::app::{get_app, get_provider, register_component, SDK_VERSION};
5use crate::component::types::{
6    ComponentError, DynService, InstanceFactoryOptions, InstantiationMode,
7};
8use crate::component::{Component, ComponentType};
9use crate::storage::constants::STORAGE_TYPE;
10use crate::storage::error::{internal_error, StorageResult};
11use crate::storage::reference::StorageReference;
12use crate::storage::service::FirebaseStorageImpl;
13use crate::storage::util::is_url;
14
15static STORAGE_COMPONENT_REGISTERED: LazyLock<()> = LazyLock::new(|| {
16    let component = Component::new(
17        STORAGE_TYPE,
18        Arc::new(storage_factory),
19        ComponentType::Public,
20    )
21    .with_instantiation_mode(InstantiationMode::Lazy)
22    .with_multiple_instances(true);
23    let _ = register_component(component);
24});
25
26fn storage_factory(
27    container: &crate::component::ComponentContainer,
28    options: InstanceFactoryOptions,
29) -> Result<DynService, ComponentError> {
30    let app = container.root_service::<FirebaseApp>().ok_or_else(|| {
31        ComponentError::InitializationFailed {
32            name: STORAGE_TYPE.to_string(),
33            reason: "Firebase app not attached to component container".to_string(),
34        }
35    })?;
36
37    let auth_provider = container.get_provider("auth-internal");
38    let app_check_provider = container.get_provider("app-check-internal");
39
40    let storage = FirebaseStorageImpl::new(
41        (*app).clone(),
42        auth_provider,
43        app_check_provider,
44        options.instance_identifier.clone(),
45        Some(SDK_VERSION.to_string()),
46    )
47    .map_err(|err| ComponentError::InitializationFailed {
48        name: STORAGE_TYPE.to_string(),
49        reason: err.to_string(),
50    })?;
51
52    Ok(Arc::new(storage) as DynService)
53}
54
55fn ensure_registered() {
56    LazyLock::force(&STORAGE_COMPONENT_REGISTERED);
57}
58
59pub fn register_storage_component() {
60    ensure_registered();
61}
62
63pub async fn get_storage_for_app(
64    app: Option<FirebaseApp>,
65    bucket_url: Option<&str>,
66) -> StorageResult<Arc<FirebaseStorageImpl>> {
67    ensure_registered();
68    let app = match app {
69        Some(app) => app,
70        None => get_app(None)
71            .await
72            .map_err(|err| internal_error(err.to_string()))?,
73    };
74
75    let provider = get_provider(&app, STORAGE_TYPE);
76    let storage = provider
77        .get_immediate_with_options::<FirebaseStorageImpl>(bucket_url, false)
78        .map_err(|err| internal_error(err.to_string()))?
79        .ok_or_else(|| internal_error("Storage component did not return an instance"))?;
80
81    Ok(storage)
82}
83
84pub fn storage_ref_from_storage(
85    storage: &FirebaseStorageImpl,
86    path_or_url: Option<&str>,
87) -> StorageResult<StorageReference> {
88    storage.reference_from_path(path_or_url)
89}
90
91pub fn storage_ref_from_reference(
92    reference: &StorageReference,
93    path: Option<&str>,
94) -> StorageResult<StorageReference> {
95    match path {
96        Some(segment) if is_url(segment) => {
97            // Mirrors JS behaviour: URLs must be paired with a Storage instance, not a reference.
98            Err(internal_error(
99                "Use storage_ref_from_storage for URL-based references",
100            ))
101        }
102        Some(segment) => Ok(reference.child(segment)),
103        None => Ok(reference.clone()),
104    }
105}
106
107pub fn connect_storage_emulator(
108    storage: &FirebaseStorageImpl,
109    host: &str,
110    port: u16,
111    mock_user_token: Option<String>,
112) -> StorageResult<()> {
113    storage.connect_emulator(host, port, mock_user_token)
114}
115
116pub fn delete_storage_instance(storage: &FirebaseStorageImpl) {
117    let bucket = storage.bucket();
118    if bucket.is_some() {
119        // Components currently lack explicit cleanup hooks; placeholder for parity.
120    }
121}