@sudobility/di
v1.5.65
Published
Platform-agnostic dependency injection interfaces for React and React Native projects with platform-specific implementations
Downloads
2,864
Maintainers
Readme
@sudobility/di
Platform-agnostic dependency injection for React and React Native with automatic platform detection.
Overview
This library provides unified DI services that work identically on Web and React Native. The bundler automatically selects the correct platform implementation, so you write the same code for both platforms.
Features
- Unified API: Same code works on Web and React Native
- Automatic Platform Detection: Bundler selects correct implementation
- TypeScript First: Fully typed interfaces and implementations
- Lazy Loading: Native modules loaded only when used
- Singleton Management: Built-in service lifecycle management
- Modular: Import only what you need
Installation
npm install @sudobility/diOptional Peer Dependencies
Web:
npm install firebaseReact Native:
npm install @react-native-async-storage/async-storage
npm install @react-native-community/netinfo
npm install @react-native-firebase/app @react-native-firebase/analytics
npm install @react-native-firebase/remote-config @react-native-firebase/messaging
npm install @notifee/react-nativeQuick Start
1. Initialize Services
// main.tsx or App.tsx
import {
initializeStorageService,
initializeNetworkService,
initializeFirebaseService,
} from '@sudobility/di';
// Call before rendering app
initializeStorageService();
initializeNetworkService();
initializeFirebaseService(firebaseConfig);2. Use Services
import {
getStorageService,
getNetworkService,
getFirebaseService,
networkClient,
} from '@sudobility/di';
// Storage
const storage = getStorageService();
await storage.setItem('token', 'abc123');
const token = await storage.getItem('token');
// Network requests
const response = await networkClient.get<User>('/api/user');
// Network status
const network = getNetworkService();
const isOnline = network.isOnline();
// Firebase
const firebase = getFirebaseService();
firebase.analytics.logEvent('screen_view', { screen: 'Home' });API Reference
Storage
import { getStorageService, storage } from '@sudobility/di';
const storageService = getStorageService();
// Basic operations
await storageService.setItem('key', 'value');
const value = await storageService.getItem('key');
await storageService.removeItem('key');
await storageService.clear();
const keys = await storageService.getAllKeys();Network Requests
import { networkClient } from '@sudobility/di';
// GET
const response = await networkClient.get<User>('/api/user');
if (response.ok) {
console.log(response.data);
}
// POST with body
const result = await networkClient.post<Result>('/api/submit', {
name: 'John',
email: '[email protected]'
});
// PUT and DELETE
await networkClient.put('/api/user/1', { name: 'Jane' });
await networkClient.delete('/api/user/1');
// With options
const response = await networkClient.get('/api/data', {
headers: { 'Authorization': 'Bearer token' },
timeout: 5000,
});Network Status
import { getNetworkService } from '@sudobility/di';
const network = getNetworkService();
// Check connection
const isOnline = network.isOnline();
// Watch for changes
const unsubscribe = network.watchNetworkStatus((online) => {
console.log('Connection:', online ? 'Online' : 'Offline');
});
// Cleanup when done
unsubscribe();Firebase
import { getFirebaseService } from '@sudobility/di';
const firebase = getFirebaseService();
// Analytics
firebase.analytics.logEvent('purchase', { item: 'premium', price: 9.99 });
firebase.analytics.setUserId('user123');
firebase.analytics.setUserProperties({ plan: 'pro' });
// Remote Config
await firebase.remoteConfig.fetchAndActivate();
const showFeature = firebase.remoteConfig.getValue('new_feature').asBoolean();
const apiUrl = firebase.remoteConfig.getValue('api_url').asString();
const maxItems = firebase.remoteConfig.getValue('max_items').asNumber();
// Messaging (FCM)
const granted = await firebase.messaging.requestPermission();
if (granted) {
const token = await firebase.messaging.getToken();
console.log('FCM Token:', token);
}
const unsubscribe = firebase.messaging.onMessage((message) => {
console.log('Push notification:', message.notification?.title);
});React Native Only Services
import {
getThemeService,
getNavigationService,
getNotificationService,
} from '@sudobility/di';
// Theme
const theme = getThemeService();
const isDark = theme.isDarkMode();
theme.applyTheme('dark'); // 'light' | 'dark' | 'system'
theme.watchSystemTheme((mode) => {
console.log('System theme:', mode);
});
// Navigation (call setNavigationRef first in App.tsx)
const nav = getNavigationService();
nav.navigate('Profile', { userId: '123' });
nav.goBack();
nav.replace('Home');
// Notifications
const notifications = getNotificationService();
await notifications.requestPermission();
await notifications.showNotification('Hello', {
body: 'You have a new message',
data: { screen: 'inbox' }
});
await notifications.setBadgeCount(5);Import Paths
// Auto-detect platform (recommended)
import { ... } from '@sudobility/di';
// Explicit web imports
import { ... } from '@sudobility/di/web';
// Explicit React Native imports
import { ... } from '@sudobility/di/rn';
// Interfaces only (no implementations)
import type { ... } from '@sudobility/di/interfaces';
// Mocks for testing
import { MockStorageService, MockNetworkClient } from '@sudobility/di/mocks';Unified Exports
These exports work identically on both platforms:
| Export | Description |
|--------|-------------|
| networkClient | Pre-configured network client instance |
| storage | Basic storage instance |
| advancedStorage | Storage with TTL support |
| getStorageService() | Get storage service singleton |
| getNetworkService() | Get network service singleton |
| getFirebaseService() | Get Firebase service singleton |
| initializeStorageService() | Initialize storage singleton |
| initializeNetworkService() | Initialize network singleton |
| initializeFirebaseService() | Initialize Firebase singleton |
| StorageService | Storage service class |
| NetworkService | Network service class |
| FirebaseService | Firebase service class |
Platform-Specific Exports
Web Only
WebStorageService,WebNetworkService,WebFirebaseServicewebStorage,webNetworkClientWebUINavigationService
React Native Only
RNStorageService,RNNetworkService,RNFirebaseServicernStorage,rnNetworkClientgetThemeService(),getNavigationService(),getNotificationService()RNThemeService,RNNavigationService,RNNotificationService
Testing with Mocks
import {
MockStorageService,
MockNetworkClient,
MockAnalyticsClient,
} from '@sudobility/di/mocks';
// In your tests
const mockStorage = new MockStorageService();
await mockStorage.setItem('key', 'value');
expect(await mockStorage.getItem('key')).toBe('value');
const mockNetwork = new MockNetworkClient();
// Configure mock responses...Firebase China proxy
Firebase Auth, Remote Config, Installations and Analytics are unreachable from
mainland China. di can route that traffic through a reverse proxy
(see the firebase-china-proxy project).
The library holds no default and reads no environment variable. The app
supplies the origin, once, at startup — before initializeApp():
import { setFirebaseProxy } from '@sudobility/di';
// web
setFirebaseProxy(import.meta.env.VITE_FIREBASE_PROXY);
// react native
setFirebaseProxy(process.env.EXPO_PUBLIC_FIREBASE_PROXY);Not set — Firebase is used directly. Nothing is patched, no probe runs.
Set — di decides whether this device actually needs the proxy: a cached
verdict (24h) applies instantly, a mainland-China timezone pre-enables routing
so early requests are not lost, and a probe against Google then confirms or
corrects it.
Consumers never ask "am I in China?" — they ask di:
getFirebaseProxyOrigin(); // the origin while routing, else null
isFirebaseProxyActive(); // same answer as a boolean
await firebaseProxyReady(); // resolves once detection settlesnull means "use Firebase directly", whether because nothing was configured
or because Google turned out to be reachable. Callers do not need to know
which, and must not re-implement the distinction.
React Native limitation: the proxy is a JavaScript fetch wrapper, so it
covers the Firebase JS SDK only. @react-native-firebase native modules
do not route through JS fetch and will still reach Google directly. RN apps
using the native SDKs are not fully covered by this mechanism.
License
MIT
