import axios, { AxiosInstance, AxiosResponse } from 'axios';
import { z } from 'zod';
// Types and interfaces
export interface UberConfig {
clientId: string;
clientSecret: string;
serverToken?: string;
accessToken?: string;
baseUrl: string;
sandbox: boolean;
}
// Zod schemas for validation
export const LocationSchema = z.object({
latitude: z.number(),
longitude: z.number(),
address: z.string().optional(),
});
export const RideEstimateSchema = z.object({
product_id: z.string(),
display_name: z.string(),
estimate: z.string(),
low_estimate: z.number().optional(),
high_estimate: z.number().optional(),
surge_multiplier: z.number().optional(),
duration: z.number().optional(),
distance: z.number().optional(),
});
export const RideRequestSchema = z.object({
product_id: z.string(),
start_latitude: z.number(),
start_longitude: z.number(),
end_latitude: z.number().optional(),
end_longitude: z.number().optional(),
seat_count: z.number().optional(),
fare_id: z.string().optional(),
});
export const RideSchema = z.object({
request_id: z.string(),
status: z.string(),
driver: z.object({
name: z.string(),
phone_number: z.string(),
picture_url: z.string().optional(),
rating: z.number().optional(),
}).optional(),
vehicle: z.object({
make: z.string(),
model: z.string(),
license_plate: z.string(),
picture_url: z.string().optional(),
}).optional(),
location: z.object({
latitude: z.number(),
longitude: z.number(),
bearing: z.number().optional(),
}).optional(),
eta: z.number().optional(),
surge_multiplier: z.number().optional(),
});
export const ProductSchema = z.object({
product_id: z.string(),
description: z.string(),
display_name: z.string(),
capacity: z.number(),
image: z.string().optional(),
});
export type Location = z.infer<typeof LocationSchema>;
export type RideEstimate = z.infer<typeof RideEstimateSchema>;
export type RideRequest = z.infer<typeof RideRequestSchema>;
export type Ride = z.infer<typeof RideSchema>;
export type Product = z.infer<typeof ProductSchema>;
// Error types
export class UberAPIError extends Error {
constructor(
public status: number,
public code: string,
message: string,
public details?: any
) {
super(message);
this.name = 'UberAPIError';
}
}
// Turkish language messages
const turkishMessages = {
errors: {
unauthorized: 'Uber hesabına erişim yetkisi yok',
not_found: 'İstenen kaynak bulunamadı',
bad_request: 'Geçersiz istek parametreleri',
forbidden: 'Bu işlem için yetki yok',
rate_limit: 'API rate limiti aşıldı, lütfen bekleyin',
server_error: 'Uber sunucu hatası',
network_error: 'Bağlantı hatası',
validation_error: 'Veri doğrulama hatası',
},
ride_status: {
processing: 'İşleniyor',
no_drivers_available: 'Uygun sürücü yok',
driver_canceled: 'Sürücü iptal etti',
rider_canceled: 'Yolcu iptal etti',
accepted: 'Kabul edildi',
arriving: 'Sürücü geliyor',
in_progress: 'Yolculuk devam ediyor',
completed: 'Tamamlandı',
}
};
export class UberAPI {
private client: AxiosInstance;
private config: UberConfig;
constructor(config: UberConfig) {
this.config = config;
this.client = axios.create({
baseURL: config.baseUrl,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
'Accept-Language': 'tr-TR,en-US;q=0.9',
},
});
this.setupInterceptors();
}
private setupInterceptors(): void {
// Request interceptor for authentication
this.client.interceptors.request.use((config) => {
if (this.config.accessToken) {
config.headers.Authorization = `Bearer ${this.config.accessToken}`;
} else if (this.config.serverToken) {
config.headers.Authorization = `Token ${this.config.serverToken}`;
}
return config;
});
// Response interceptor for error handling
this.client.interceptors.response.use(
(response) => response,
(error) => {
if (error.response) {
const { status, data } = error.response;
let message = turkishMessages.errors.server_error;
switch (status) {
case 401:
message = turkishMessages.errors.unauthorized;
break;
case 403:
message = turkishMessages.errors.forbidden;
break;
case 404:
message = turkishMessages.errors.not_found;
break;
case 400:
message = turkishMessages.errors.bad_request;
break;
case 429:
message = turkishMessages.errors.rate_limit;
break;
}
throw new UberAPIError(status, data?.code || 'unknown', message, data);
} else if (error.request) {
throw new UberAPIError(0, 'network_error', turkishMessages.errors.network_error);
} else {
throw new UberAPIError(0, 'unknown', error.message);
}
}
);
}
/**
* Get available products (ride types) for a location
*/
async getProducts(latitude: number, longitude: number): Promise<Product[]> {
try {
const response = await this.client.get('/v1.2/products', {
params: { latitude, longitude },
});
return response.data.products.map((product: any) =>
ProductSchema.parse(product)
);
} catch (error) {
if (error instanceof UberAPIError) throw error;
throw new UberAPIError(0, 'validation_error', turkishMessages.errors.validation_error);
}
}
/**
* Get price estimate for a ride
*/
async getPriceEstimate(
startLatitude: number,
startLongitude: number,
endLatitude: number,
endLongitude: number
): Promise<RideEstimate[]> {
try {
const response = await this.client.get('/v1.2/estimates/price', {
params: {
start_latitude: startLatitude,
start_longitude: startLongitude,
end_latitude: endLatitude,
end_longitude: endLongitude,
},
});
return response.data.prices.map((estimate: any) =>
RideEstimateSchema.parse(estimate)
);
} catch (error) {
if (error instanceof UberAPIError) throw error;
throw new UberAPIError(0, 'validation_error', turkishMessages.errors.validation_error);
}
}
/**
* Get time estimate for a ride
*/
async getTimeEstimate(
startLatitude: number,
startLongitude: number,
productId?: string
): Promise<any> {
try {
const params: any = {
start_latitude: startLatitude,
start_longitude: startLongitude,
};
if (productId) {
params.product_id = productId;
}
const response = await this.client.get('/v1.2/estimates/time', {
params,
});
return response.data.times;
} catch (error) {
if (error instanceof UberAPIError) throw error;
throw new UberAPIError(0, 'validation_error', turkishMessages.errors.validation_error);
}
}
/**
* Request a ride
*/
async requestRide(rideRequest: RideRequest): Promise<Ride> {
try {
const validatedRequest = RideRequestSchema.parse(rideRequest);
const response = await this.client.post('/v1.2/requests', validatedRequest);
return RideSchema.parse(response.data);
} catch (error) {
if (error instanceof UberAPIError) throw error;
throw new UberAPIError(0, 'validation_error', turkishMessages.errors.validation_error);
}
}
/**
* Get current ride details
*/
async getCurrentRide(): Promise<Ride | null> {
try {
const response = await this.client.get('/v1.2/requests/current');
return RideSchema.parse(response.data);
} catch (error) {
if (error instanceof UberAPIError && error.status === 404) {
return null; // No current ride
}
throw error;
}
}
/**
* Get ride details by request ID
*/
async getRideDetails(requestId: string): Promise<Ride> {
try {
const response = await this.client.get(`/v1.2/requests/${requestId}`);
return RideSchema.parse(response.data);
} catch (error) {
if (error instanceof UberAPIError) throw error;
throw new UberAPIError(0, 'validation_error', turkishMessages.errors.validation_error);
}
}
/**
* Cancel a ride
*/
async cancelRide(requestId: string): Promise<void> {
try {
await this.client.delete(`/v1.2/requests/${requestId}`);
} catch (error) {
if (error instanceof UberAPIError) throw error;
throw new UberAPIError(0, 'validation_error', turkishMessages.errors.validation_error);
}
}
/**
* Get ride history
*/
async getRideHistory(offset?: number, limit?: number): Promise<any> {
try {
const params: any = {};
if (offset !== undefined) params.offset = offset;
if (limit !== undefined) params.limit = limit;
const response = await this.client.get('/v1.2/history', { params });
return response.data;
} catch (error) {
if (error instanceof UberAPIError) throw error;
throw new UberAPIError(0, 'validation_error', turkishMessages.errors.validation_error);
}
}
/**
* Rate a driver
*/
async rateDriver(requestId: string, rating: number, feedback?: string): Promise<void> {
try {
const ratingData: any = { rating };
if (feedback) {
ratingData.feedback = feedback;
}
await this.client.patch(`/v1.2/requests/${requestId}`, ratingData);
} catch (error) {
if (error instanceof UberAPIError) throw error;
throw new UberAPIError(0, 'validation_error', turkishMessages.errors.validation_error);
}
}
/**
* Get user profile
*/
async getUserProfile(): Promise<any> {
try {
const response = await this.client.get('/v1.2/me');
return response.data;
} catch (error) {
if (error instanceof UberAPIError) throw error;
throw new UberAPIError(0, 'validation_error', turkishMessages.errors.validation_error);
}
}
/**
* Update access token
*/
updateAccessToken(accessToken: string): void {
this.config.accessToken = accessToken;
}
/**
* Get OAuth authorization URL
*/
getAuthorizationUrl(redirectUri: string, state?: string): string {
const params = new URLSearchParams({
response_type: 'code',
client_id: this.config.clientId,
redirect_uri: redirectUri,
scope: 'profile request history',
});
if (state) {
params.append('state', state);
}
return `https://login.uber.com/oauth/v2/authorize?${params.toString()}`;
}
/**
* Exchange authorization code for access token
*/
async exchangeCodeForToken(
code: string,
redirectUri: string
): Promise<{ access_token: string; refresh_token: string; expires_in: number }> {
try {
const response = await axios.post('https://login.uber.com/oauth/v2/token', {
grant_type: 'authorization_code',
client_id: this.config.clientId,
client_secret: this.config.clientSecret,
redirect_uri: redirectUri,
code,
});
return response.data;
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
throw new UberAPIError(
error.response.status,
error.response.data?.error || 'token_exchange_failed',
'Token değişimi başarısız oldu'
);
}
throw new UberAPIError(0, 'network_error', turkishMessages.errors.network_error);
}
}
/**
* Translate ride status to Turkish
*/
translateStatus(status: string): string {
return (turkishMessages.ride_status as any)[status] || status;
}
}