Balance socket entegrasyonu
Bakiye güncellemeleri WebSocket client
Bakiye Socket Gateway - React Native Entegrasyon Dokümantasyonu
Migration rehberi
Yapılandırma sırası için Migration şablonu. Yeni düzenlemelerde Amaç → Önkoşullar → Endpoint → Request/Response → Hata kodları → Client adımları → İlgili sayfalar bölümlerini tercih edin.
Genel Bakış
Bakiye Socket Gateway, kullanıcıların bakiyelerindeki değişiklikleri gerçek zamanlı olarak dinleyebilmelerini sağlar. Tüm bakiye işlemleri (yükleme, yayın gelirleri, hediye giderleri/gelirleri, superlike giderleri/gelirleri, kredi transferleri, yayın izleme ücretleri) socket üzerinden bildirilir.
Önemli Not: Hediye ve superlike gönderildiğinde, gönderen kullanıcının bakiyesi düşerken, yayıncıların (creator ve guest) bakiyelerine gelir eklenir. Gelir dağıtımı: Guest yoksa creator %80, guest varsa creator %40 ve guest %40 alır.
Kurulum
1. Socket.IO Client Kurulumu
npm install socket.io-client
# veya
yarn add socket.io-client2. TypeScript Tipleri (Opsiyonel)
npm install --save-dev @types/socket.io-clientTemel Kullanım
1. Socket Bağlantısı Oluşturma
import io, { Socket } from 'socket.io-client';
// Socket bağlantısı oluştur
const socket = io('YOUR_API_BASE_URL/balance', {
transports: ['websocket', 'polling'],
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5,
});2. Bağlantı Event'leri
// Bağlantı başarılı olduğunda
socket.on('connect', () => {
console.log('Balance socket bağlantısı kuruldu');
});
// Bağlantı kesildiğinde
socket.on('disconnect', (reason) => {
console.log('Balance socket bağlantısı kesildi:', reason);
});
// Bağlantı hatası
socket.on('connect_error', (error) => {
console.error('Balance socket bağlantı hatası:', error);
});3. Bakiye Dinlemeye Başlama (Subscribe)
// Token ile subscribe ol
socket.emit('subscribe', {
token: 'YOUR_JWT_TOKEN', // Bearer token veya sadece token
});
// Subscribe başarılı
socket.on('subscribeSuccess', (data) => {
console.log('Bakiye dinlemeye başlandı:', data);
// data: { userId: string, room: string }
});
// Subscribe hatası
socket.on('subscribeError', (error) => {
console.error('Subscribe hatası:', error);
// error: { code: string, message: string }
});4. Bakiye Değişikliklerini Dinleme
socket.on('balanceChange', (event: BalanceChangeEvent) => {
console.log('Bakiye değişikliği:', event);
// Event yapısı:
// {
// userId: string,
// previousBalance: number,
// newBalance: number,
// changeAmount: number,
// transactionType: BalanceTransactionType,
// description: string,
// metadata?: BalanceChangeMetadata,
// timestamp: Date
// }
// UI'ı güncelle
updateBalanceInUI(event.newBalance);
});5. Bakiye Dinlemeyi Bırakma (Unsubscribe)
socket.emit('unsubscribe');
socket.on('unsubscribeSuccess', () => {
console.log('Bakiye dinleme durduruldu');
});TypeScript Tipleri
BalanceTransactionType Enum
export enum BalanceTransactionType {
DEPOSIT = 'DEPOSIT', // Bakiye yükleme
WITHDRAWAL = 'WITHDRAWAL', // Para çekme
GIFT_SENT = 'GIFT_SENT', // Hediye gönderme (gönderen için)
GIFT_RECEIVED = 'GIFT_RECEIVED', // Hediye alma
GIFT_REVENUE = 'GIFT_REVENUE', // Hediye geliri (creator/guest için)
STREAM_REVENUE = 'STREAM_REVENUE', // Yayın geliri (creator/guest için)
STREAM_CHARGE = 'STREAM_CHARGE', // Yayın izleme ücreti
SUPERLIKE = 'SUPERLIKE', // Superlike gönderme (gönderen için)
SUPERLIKE_REVENUE = 'SUPERLIKE_REVENUE', // Superlike geliri (creator/guest için)
CREDIT_TRANSFER_SENT = 'CREDIT_TRANSFER_SENT', // Kredi transferi (gönderen)
CREDIT_TRANSFER_RECEIVED = 'CREDIT_TRANSFER_RECEIVED', // Kredi transferi (alan)
}BalanceChangeEvent Interface
export interface BalanceChangeMetadata {
streamId?: string;
giftId?: string;
transactionId?: string;
creditPackageId?: string;
recipientId?: string;
senderId?: string;
role?: 'creator' | 'guest';
amount?: number;
[key: string]: any;
}
export interface BalanceChangeEvent {
userId: string;
previousBalance: number;
newBalance: number;
changeAmount: number; // Pozitif veya negatif
transactionType: BalanceTransactionType;
description: string;
metadata?: BalanceChangeMetadata;
timestamp: Date;
}React Native Hook Örneği
useBalanceSocket Hook
import { useEffect, useRef, useState } from 'react';
import io, { Socket } from 'socket.io-client';
import { BalanceChangeEvent, BalanceTransactionType } from './types';
interface UseBalanceSocketOptions {
token: string;
apiBaseUrl: string;
onBalanceChange?: (event: BalanceChangeEvent) => void;
onError?: (error: { code: string; message: string }) => void;
}
export const useBalanceSocket = ({
token,
apiBaseUrl,
onBalanceChange,
onError,
}: UseBalanceSocketOptions) => {
const [isConnected, setIsConnected] = useState(false);
const [isSubscribed, setIsSubscribed] = useState(false);
const [currentBalance, setCurrentBalance] = useState<number | null>(null);
const socketRef = useRef<Socket | null>(null);
useEffect(() => {
// Socket bağlantısı oluştur
const socket = io(`${apiBaseUrl}/balance`, {
transports: ['websocket', 'polling'],
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5,
});
socketRef.current = socket;
// Bağlantı event'leri
socket.on('connect', () => {
console.log('Balance socket bağlandı');
setIsConnected(true);
// Bağlantı kurulduğunda otomatik subscribe
if (token) {
socket.emit('subscribe', { token });
}
});
socket.on('disconnect', () => {
console.log('Balance socket bağlantısı kesildi');
setIsConnected(false);
setIsSubscribed(false);
});
socket.on('connect_error', (error) => {
console.error('Balance socket bağlantı hatası:', error);
setIsConnected(false);
});
// Subscribe event'leri
socket.on('subscribeSuccess', (data) => {
console.log('Bakiye dinlemeye başlandı:', data);
setIsSubscribed(true);
});
socket.on('subscribeError', (error) => {
console.error('Subscribe hatası:', error);
setIsSubscribed(false);
onError?.(error);
});
// Bakiye değişikliği
socket.on('balanceChange', (event: BalanceChangeEvent) => {
console.log('Bakiye değişikliği:', event);
setCurrentBalance(event.newBalance);
onBalanceChange?.(event);
});
// Unsubscribe event
socket.on('unsubscribeSuccess', () => {
console.log('Bakiye dinleme durduruldu');
setIsSubscribed(false);
});
// Cleanup
return () => {
if (socket.connected) {
socket.emit('unsubscribe');
}
socket.disconnect();
};
}, [apiBaseUrl, token, onBalanceChange, onError]);
// Manuel subscribe/unsubscribe fonksiyonları
const subscribe = (newToken?: string) => {
if (socketRef.current?.connected) {
socketRef.current.emit('subscribe', {
token: newToken || token,
});
}
};
const unsubscribe = () => {
if (socketRef.current?.connected) {
socketRef.current.emit('unsubscribe');
}
};
return {
isConnected,
isSubscribed,
currentBalance,
subscribe,
unsubscribe,
};
};Kullanım Örneği
Component'te Kullanım
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useBalanceSocket } from './hooks/useBalanceSocket';
import { BalanceChangeEvent, BalanceTransactionType } from './types';
const BalanceScreen: React.FC = () => {
const token = 'YOUR_JWT_TOKEN'; // Auth context'ten alınabilir
const apiBaseUrl = 'https://api.example.com';
const handleBalanceChange = (event: BalanceChangeEvent) => {
// Bakiye değişikliğine göre UI güncelle
console.log('Yeni bakiye:', event.newBalance);
console.log('Değişim:', event.changeAmount);
console.log('İşlem tipi:', event.transactionType);
console.log('Açıklama:', event.description);
// İşlem tipine göre farklı işlemler yapılabilir
switch (event.transactionType) {
case BalanceTransactionType.DEPOSIT:
showSuccessMessage('Bakiye yüklendi!');
break;
case BalanceTransactionType.STREAM_CHARGE:
showInfoMessage('Yayın izleme ücreti kesildi');
break;
case BalanceTransactionType.STREAM_REVENUE:
showSuccessMessage('Yayın geliri eklendi!');
break;
case BalanceTransactionType.GIFT_SENT:
showInfoMessage('Hediye gönderildi');
break;
case BalanceTransactionType.GIFT_REVENUE:
showSuccessMessage('Hediye geliri eklendi!');
break;
case BalanceTransactionType.SUPERLIKE:
showInfoMessage('Superlike gönderildi');
break;
case BalanceTransactionType.SUPERLIKE_REVENUE:
showSuccessMessage('Superlike geliri eklendi!');
break;
// ... diğer durumlar
}
};
const { isConnected, isSubscribed, currentBalance } = useBalanceSocket({
token,
apiBaseUrl,
onBalanceChange: handleBalanceChange,
onError: (error) => {
console.error('Socket hatası:', error);
// Hata mesajı göster
},
});
return (
<View style={styles.container}>
<Text style={styles.title}>Bakiye</Text>
<View style={styles.statusContainer}>
<Text>
Durum: {isConnected ? 'Bağlı' : 'Bağlı Değil'} |{' '}
{isSubscribed ? 'Dinleniyor' : 'Dinlenmiyor'}
</Text>
</View>
<View style={styles.balanceContainer}>
<Text style={styles.balanceLabel}>Mevcut Bakiye</Text>
<Text style={styles.balanceAmount}>
{currentBalance !== null ? `${currentBalance} kredi` : 'Yükleniyor...'}
</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
statusContainer: {
marginBottom: 20,
},
balanceContainer: {
backgroundColor: '#f5f5f5',
padding: 20,
borderRadius: 10,
},
balanceLabel: {
fontSize: 14,
color: '#666',
marginBottom: 5,
},
balanceAmount: {
fontSize: 32,
fontWeight: 'bold',
color: '#000',
},
});
export default BalanceScreen;Context API ile Global Kullanım
BalanceContext
import React, { createContext, useContext, ReactNode } from 'react';
import { useBalanceSocket } from '../hooks/useBalanceSocket';
import { BalanceChangeEvent } from '../types';
interface BalanceContextType {
isConnected: boolean;
isSubscribed: boolean;
currentBalance: number | null;
subscribe: (token?: string) => void;
unsubscribe: () => void;
}
const BalanceContext = createContext<BalanceContextType | undefined>(undefined);
export const BalanceProvider: React.FC<{
children: ReactNode;
token: string;
apiBaseUrl: string;
}> = ({ children, token, apiBaseUrl }) => {
const handleBalanceChange = (event: BalanceChangeEvent) => {
// Global balance change handler
// Örneğin: Redux store'a kaydet, notification göster, vs.
console.log('Global balance change:', event);
};
const balanceSocket = useBalanceSocket({
token,
apiBaseUrl,
onBalanceChange: handleBalanceChange,
});
return (
<BalanceContext.Provider value={balanceSocket}>
{children}
</BalanceContext.Provider>
);
};
export const useBalance = () => {
const context = useContext(BalanceContext);
if (!context) {
throw new Error('useBalance must be used within BalanceProvider');
}
return context;
};App.tsx'te Kullanım
import React from 'react';
import { BalanceProvider } from './contexts/BalanceContext';
import { useAuth } from './hooks/useAuth';
const App: React.FC = () => {
const { token } = useAuth();
const apiBaseUrl = 'https://api.example.com';
return (
<BalanceProvider token={token} apiBaseUrl={apiBaseUrl}>
{/* Diğer component'ler */}
</BalanceProvider>
);
};Hata Yönetimi
Hata Kodları
export enum BalanceSocketErrorCode {
AUTH_REQUIRED = 'AUTH_REQUIRED',
TOKEN_INVALID = 'TOKEN_INVALID',
USER_INVALID = 'USER_INVALID',
}
// Hata yönetimi örneği
socket.on('subscribeError', (error) => {
switch (error.code) {
case BalanceSocketErrorCode.AUTH_REQUIRED:
// Token eksik, login sayfasına yönlendir
navigateToLogin();
break;
case BalanceSocketErrorCode.TOKEN_INVALID:
// Token geçersiz, token yenile
refreshToken();
break;
case BalanceSocketErrorCode.USER_INVALID:
// Kullanıcı geçersiz
console.error('Geçersiz kullanıcı');
break;
default:
console.error('Bilinmeyen hata:', error);
}
});Best Practices
1. Token Yönetimi
// Token değiştiğinde yeniden subscribe et
useEffect(() => {
if (socketRef.current?.connected && token) {
socketRef.current.emit('subscribe', { token });
}
}, [token]);2. Reconnection Handling
socket.on('reconnect', (attemptNumber) => {
console.log('Yeniden bağlandı, deneme:', attemptNumber);
// Token ile yeniden subscribe
if (token) {
socket.emit('subscribe', { token });
}
});3. Background/Foreground Handling
import { AppState } from 'react-native';
useEffect(() => {
const subscription = AppState.addEventListener('change', (nextAppState) => {
if (nextAppState === 'active') {
// Uygulama foreground'a geldiğinde bağlantıyı kontrol et
if (!socketRef.current?.connected) {
socketRef.current?.connect();
}
} else if (nextAppState === 'background') {
// Uygulama background'a gittiğinde unsubscribe et (opsiyonel)
// socketRef.current?.emit('unsubscribe');
}
});
return () => {
subscription.remove();
};
}, []);4. Performance Optimizasyonu
// Balance değişikliklerini debounce et
import { debounce } from 'lodash';
const debouncedBalanceUpdate = debounce((event: BalanceChangeEvent) => {
updateUI(event);
}, 300);
socket.on('balanceChange', debouncedBalanceUpdate);5. Memory Leak Önleme
useEffect(() => {
const socket = io(/* ... */);
// Cleanup function
return () => {
socket.off('balanceChange'); // Event listener'ları temizle
socket.off('subscribeSuccess');
socket.off('subscribeError');
socket.disconnect();
};
}, []);Event Örnekleri
1. Bakiye Yükleme (DEPOSIT)
{
"userId": "507f1f77bcf86cd799439011",
"previousBalance": 1000,
"newBalance": 1200,
"changeAmount": 200,
"transactionType": "DEPOSIT",
"description": "Bakiye yükleme - RevenueCat (APP_STORE)",
"metadata": {
"transactionId": "transaction_123",
"creditPackageId": "package_456",
"productId": "product_789"
},
"timestamp": "2024-01-16T15:30:00.000Z"
}2. Yayın İzleme Ücreti (STREAM_CHARGE)
{
"userId": "507f1f77bcf86cd799439011",
"previousBalance": 1000,
"newBalance": 990,
"changeAmount": -10,
"transactionType": "STREAM_CHARGE",
"description": "Yayın izleme ücreti - Dakika bazlı ücret kesme",
"metadata": {
"streamId": "stream_123",
"amount": 10
},
"timestamp": "2024-01-16T15:30:00.000Z"
}3. Yayın Geliri (STREAM_REVENUE)
{
"userId": "507f1f77bcf86cd799439011",
"previousBalance": 1000,
"newBalance": 1400,
"changeAmount": 400,
"transactionType": "STREAM_REVENUE",
"description": "Yayın geliri - Creator (guest ile)",
"metadata": {
"streamId": "stream_123",
"role": "creator",
"amount": 400
},
"timestamp": "2024-01-16T15:30:00.000Z"
}4. Hediye Gönderme (GIFT_SENT)
{
"userId": "507f1f77bcf86cd799439011",
"previousBalance": 1000,
"newBalance": 900,
"changeAmount": -100,
"transactionType": "GIFT_SENT",
"description": "Hediye gönderildi - 100 kredi",
"metadata": {
"giftId": "gift_123",
"streamId": "stream_456",
"giftTypeId": "gift_type_789"
},
"timestamp": "2024-01-16T15:30:00.000Z"
}5. Superlike Gönderme (SUPERLIKE)
{
"userId": "507f1f77bcf86cd799439011",
"previousBalance": 1000,
"newBalance": 990,
"changeAmount": -10,
"transactionType": "SUPERLIKE",
"description": "Superlike gönderildi",
"metadata": {
"streamId": "stream_123"
},
"timestamp": "2024-01-16T15:30:00.000Z"
}6. Hediye Geliri (GIFT_REVENUE) - Creator
{
"userId": "507f1f77bcf86cd799439011",
"previousBalance": 1000,
"newBalance": 1080,
"changeAmount": 80,
"transactionType": "GIFT_REVENUE",
"description": "Hediye geliri - Creator (guest yok)",
"metadata": {
"streamId": "stream_123",
"role": "creator",
"amount": 80
},
"timestamp": "2024-01-16T15:30:00.000Z"
}7. Hediye Geliri (GIFT_REVENUE) - Guest
{
"userId": "507f1f77bcf86cd799439011",
"previousBalance": 500,
"newBalance": 540,
"changeAmount": 40,
"transactionType": "GIFT_REVENUE",
"description": "Hediye geliri - Guest",
"metadata": {
"streamId": "stream_123",
"role": "guest",
"amount": 40
},
"timestamp": "2024-01-16T15:30:00.000Z"
}8. Superlike Geliri (SUPERLIKE_REVENUE) - Creator
{
"userId": "507f1f77bcf86cd799439011",
"previousBalance": 1000,
"newBalance": 1008,
"changeAmount": 8,
"transactionType": "SUPERLIKE_REVENUE",
"description": "Superlike geliri - Creator (guest yok)",
"metadata": {
"streamId": "stream_123",
"role": "creator",
"amount": 8
},
"timestamp": "2024-01-16T15:30:00.000Z"
}9. Superlike Geliri (SUPERLIKE_REVENUE) - Guest
{
"userId": "507f1f77bcf86cd799439011",
"previousBalance": 500,
"newBalance": 504,
"changeAmount": 4,
"transactionType": "SUPERLIKE_REVENUE",
"description": "Superlike geliri - Guest",
"metadata": {
"streamId": "stream_123",
"role": "guest",
"amount": 4
},
"timestamp": "2024-01-16T15:30:00.000Z"
}10. Kredi Transferi (CREDIT_TRANSFER)
{
"userId": "507f1f77bcf86cd799439011",
"previousBalance": 1000,
"newBalance": 800,
"changeAmount": -200,
"transactionType": "CREDIT_TRANSFER_SENT",
"description": "Kredi transferi - recipient_user_id kullanıcısına gönderildi",
"metadata": {
"recipientId": "recipient_user_id",
"transferredAmount": 200
},
"timestamp": "2024-01-16T15:30:00.000Z"
}Test Etme
Mock Socket Test
import { renderHook, waitFor } from '@testing-library/react-native';
import { useBalanceSocket } from './useBalanceSocket';
// Mock socket.io-client
jest.mock('socket.io-client', () => {
const mockSocket = {
on: jest.fn(),
emit: jest.fn(),
disconnect: jest.fn(),
connected: true,
};
return jest.fn(() => mockSocket);
});
test('should subscribe on connect', async () => {
const { result } = renderHook(() =>
useBalanceSocket({
token: 'test-token',
apiBaseUrl: 'http://localhost',
})
);
await waitFor(() => {
expect(result.current.isSubscribed).toBe(true);
});
});Sorun Giderme
1. Bağlantı Kurulamıyor
- API base URL'in doğru olduğundan emin olun
- Network bağlantısını kontrol edin
- CORS ayarlarını kontrol edin
2. Subscribe Başarısız
- Token'ın geçerli olduğundan emin olun
- Token formatını kontrol edin (Bearer prefix gerekebilir)
- Token'ın süresi dolmamış olmalı
3. Balance Change Event'leri Gelmiyor
- Subscribe işleminin başarılı olduğundan emin olun
- Socket bağlantısının aktif olduğunu kontrol edin
- Event listener'ların doğru kurulduğunu kontrol edin
API Endpoint
Socket Namespace: /balance
Base URL: YOUR_API_BASE_URL/balance
Desteklenen Event'ler
Client → Server
subscribe- Bakiye dinlemeye başlaunsubscribe- Bakiye dinlemeyi durdur
Server → Client
connect- Bağlantı kuruldudisconnect- Bağlantı kesildiconnect_error- Bağlantı hatasısubscribeSuccess- Subscribe başarılısubscribeError- Subscribe hatasıunsubscribeSuccess- Unsubscribe başarılıbalanceChange- Bakiye değişikliği
Güvenlik
- Tüm bağlantılar JWT token ile authenticate edilir
- Kullanıcılar sadece kendi bakiyelerini dinleyebilir
- Token doğrulaması her subscribe işleminde yapılır
Gelir Dağıtım Mantığı
Hediye ve Superlike Gelirleri
Hediye veya superlike gönderildiğinde:
- Gönderen kullanıcı: Bakiyesi düşer (
GIFT_SENTveyaSUPERLIKEtransaction type) - Yayıncılar: Bakiyelerine gelir eklenir (
GIFT_REVENUEveyaSUPERLIKE_REVENUEtransaction type)
Dağıtım Oranları:
- Guest yoksa: Creator %80 alır
- Guest varsa: Creator %40, Guest %40 alır
- Kalan %20 platform'da kalır
Örnek Senaryo:
- 100 kredilik hediye gönderildi
- Guest yok: Creator 80 kredi alır
- Guest var: Creator 40 kredi, Guest 40 kredi alır
Yayın Gelirleri
Yayın izleme ücretlerinden gelen gelirler:
- Guest yoksa: Creator %80 alır
- Guest varsa: Creator %40, Guest %40 alır
- Kalan %20 platform'da kalır
Versiyon
API Versiyonu: 1.1.0
Son Güncelleme: 2024-01-16
Değişiklikler:
- v1.1.0: Hediye ve superlike gelir dağıtımı eklendi (GIFT_REVENUE, SUPERLIKE_REVENUE)