@umituz/react-native-location 1.0.26 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-location",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "Device location services for React Native with GPS, permissions, caching, and reverse geocoding",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Application Layer - Cache Port
3
+ *
4
+ * Cache abstraction for dependency inversion.
5
+ * Decouples from specific storage implementation.
6
+ */
7
+
8
+ import { LocationData } from "../../types/location.types";
9
+
10
+ export interface CachedLocationData {
11
+ location: LocationData;
12
+ timestamp: number;
13
+ }
14
+
15
+ export interface ICachePort {
16
+ /**
17
+ * Cache'den location al
18
+ */
19
+ get(key: string): Promise<CachedLocationData | null>;
20
+
21
+ /**
22
+ * Location'ı cache'le
23
+ */
24
+ set(key: string, data: CachedLocationData): Promise<void>;
25
+
26
+ /**
27
+ * Cache'i temizle
28
+ */
29
+ remove(key: string): Promise<void>;
30
+
31
+ /**
32
+ * Tüm cache'i temizle
33
+ */
34
+ clear(): Promise<void>;
35
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Application Layer - Location Service Port
3
+ *
4
+ * Core location operations interface.
5
+ * Defines what location services can do, not how.
6
+ */
7
+
8
+ import { LocationData, LocationAddress, LocationConfig } from "../../types/location.types";
9
+
10
+ export interface ILocationPort {
11
+ /**
12
+ * Mevcut konumu al
13
+ */
14
+ getCurrentPosition(): Promise<LocationData>;
15
+
16
+ /**
17
+ * Son bilinen konumu al
18
+ */
19
+ getLastKnownPosition(): Promise<LocationData | null>;
20
+
21
+ /**
22
+ * Reverse geocoding
23
+ */
24
+ reverseGeocode(latitude: number, longitude: number): Promise<LocationAddress | undefined>;
25
+
26
+ /**
27
+ * Location servisleri açık mı?
28
+ */
29
+ isLocationEnabled(): Promise<boolean>;
30
+
31
+ /**
32
+ * Permission durumu
33
+ */
34
+ getPermissionStatus(): Promise<string>;
35
+ }
36
+
37
+ /**
38
+ * Location watcher için callback interface
39
+ */
40
+ export interface ILocationWatcherPort {
41
+ /**
42
+ * Konum değişikliklerini dinle
43
+ */
44
+ watchPosition(
45
+ onSuccess: (location: LocationData) => void,
46
+ onError?: (error: Error) => void
47
+ ): Promise<void>;
48
+
49
+ /**
50
+ * Dinlemeyi durdur
51
+ */
52
+ clearWatch(): void;
53
+
54
+ /**
55
+ * Dinliyor mu?
56
+ */
57
+ isWatching(): boolean;
58
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Application Layer - Logger Port
3
+ *
4
+ * Logging abstraction for dependency inversion.
5
+ * Implementation can be easily swapped.
6
+ */
7
+
8
+ export enum LogLevel {
9
+ DEBUG = "DEBUG",
10
+ INFO = "INFO",
11
+ WARN = "WARN",
12
+ ERROR = "ERROR",
13
+ }
14
+
15
+ export interface ILoggerPort {
16
+ debug(message: string, context?: string): void;
17
+ info(message: string, context?: string): void;
18
+ warn(message: string, context?: string): void;
19
+ error(message: string, error?: unknown, context?: string): void;
20
+ }
21
+
22
+ /**
23
+ * No-op logger (production default)
24
+ */
25
+ export class NoOpLogger implements ILoggerPort {
26
+ debug(): void {}
27
+ info(): void {}
28
+ warn(): void {}
29
+ error(): void {}
30
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Domain Layer - Error Handling
3
+ *
4
+ * Merkezi error oluşturma ve validasyon.
5
+ * Tüm location error'ları buradan üretilir (Single Responsibility).
6
+ */
7
+
8
+ import { LocationErrorCode, LocationError } from "../../types/location.types";
9
+
10
+ export class LocationErrors {
11
+ private static readonly VALID_ERROR_CODES: readonly LocationErrorCode[] = [
12
+ "PERMISSION_DENIED",
13
+ "TIMEOUT",
14
+ "UNKNOWN_ERROR",
15
+ ] as const;
16
+
17
+ /**
18
+ * Error code validation
19
+ */
20
+ static isValidErrorCode(code: string): code is LocationErrorCode {
21
+ return this.VALID_ERROR_CODES.includes(code as LocationErrorCode);
22
+ }
23
+
24
+ /**
25
+ * Error object'ten code çıkarma
26
+ */
27
+ static extractErrorCode(error: unknown): LocationErrorCode {
28
+ if (error instanceof Error && "code" in error && typeof error.code === "string") {
29
+ if (this.isValidErrorCode(error.code)) {
30
+ return error.code;
31
+ }
32
+ }
33
+ return "UNKNOWN_ERROR";
34
+ }
35
+
36
+ /**
37
+ * Error mesajı çıkarma
38
+ */
39
+ static extractMessage(error: unknown): string {
40
+ if (error instanceof Error) {
41
+ return error.message;
42
+ }
43
+ return "An unknown error occurred";
44
+ }
45
+
46
+ /**
47
+ * Permission denied error
48
+ */
49
+ static permissionDenied(message = "Location permission not granted"): LocationError & Error {
50
+ return this.createError("PERMISSION_DENIED", message);
51
+ }
52
+
53
+ /**
54
+ * Timeout error
55
+ */
56
+ static timeout(message: string): LocationError & Error {
57
+ return this.createError("TIMEOUT", message);
58
+ }
59
+
60
+ /**
61
+ * Unknown error
62
+ */
63
+ static unknown(message: string): LocationError & Error {
64
+ return this.createError("UNKNOWN_ERROR", message);
65
+ }
66
+
67
+ /**
68
+ * Generic error factory
69
+ */
70
+ private static createError(code: LocationErrorCode, message: string): LocationError & Error {
71
+ const error = new Error(message) as Error & LocationError;
72
+ error.name = "LocationError";
73
+ error.code = code;
74
+ return error;
75
+ }
76
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Domain Layer - Permission Service
3
+ *
4
+ * Location permission yönetimi için merkezi servis.
5
+ * Hem LocationService hem LocationWatcher tarafından kullanılır (DRY prensibi).
6
+ */
7
+
8
+ import * as Location from "expo-location";
9
+
10
+ export class PermissionService {
11
+ /**
12
+ * Mevcut permission durumunu kontrol et
13
+ */
14
+ static async getStatus(): Promise<Location.PermissionStatus> {
15
+ try {
16
+ const { status } = await Location.getForegroundPermissionsAsync();
17
+ return status;
18
+ } catch {
19
+ return Location.PermissionStatus.UNDETERMINED;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Permission zaten verilmiş mi?
25
+ */
26
+ static async isGranted(): Promise<boolean> {
27
+ const status = await this.getStatus();
28
+ return status === "granted";
29
+ }
30
+
31
+ /**
32
+ * Permission iste (eğer gerekirse)
33
+ */
34
+ static async request(): Promise<boolean> {
35
+ try {
36
+ // Önce mevcut durumu kontrol et
37
+ if (await this.isGranted()) {
38
+ return true;
39
+ }
40
+
41
+ // Permission iste
42
+ const { status } = await Location.requestForegroundPermissionsAsync();
43
+ return status === "granted";
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Location servislerinin açık olup olmadığını kontrol et
51
+ */
52
+ static async areServicesEnabled(): Promise<boolean> {
53
+ try {
54
+ return await Location.hasServicesEnabledAsync();
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Domain Layer - Coordinates Value Object
3
+ *
4
+ * Koordinat validasyonu ve karşılaştırma işlemleri.
5
+ * Immutable value object pattern.
6
+ */
7
+
8
+ import { Coordinates } from "../../types/location.types";
9
+
10
+ export class CoordinatesVO {
11
+ private static readonly LATITUDE_MIN = -90;
12
+ private static readonly LATITUDE_MAX = 90;
13
+ private static readonly LONGITUDE_MIN = -180;
14
+ private static readonly LONGITUDE_MAX = 180;
15
+ private static readonly DEFAULT_PRECISION = 6;
16
+
17
+ /**
18
+ * Koordinat validasyonu
19
+ */
20
+ static isValid(latitude: number, longitude: number): boolean {
21
+ if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
22
+ return false;
23
+ }
24
+
25
+ return (
26
+ latitude >= this.LATITUDE_MIN &&
27
+ latitude <= this.LATITUDE_MAX &&
28
+ longitude >= this.LONGITUDE_MIN &&
29
+ longitude <= this.LONGITUDE_MAX
30
+ );
31
+ }
32
+
33
+ /**
34
+ * İki koordinat eşit mi (belirtilen hassasiyette)
35
+ */
36
+ static areEqual(
37
+ coord1: Coordinates,
38
+ coord2: Coordinates,
39
+ precision = this.DEFAULT_PRECISION
40
+ ): boolean {
41
+ const valid = [
42
+ coord1.latitude,
43
+ coord1.longitude,
44
+ coord2.latitude,
45
+ coord2.longitude,
46
+ ].every(Number.isFinite);
47
+
48
+ if (!valid) return false;
49
+
50
+ const epsilon = Math.pow(10, -precision);
51
+ return (
52
+ Math.abs(coord1.latitude - coord2.latitude) < epsilon &&
53
+ Math.abs(coord1.longitude - coord2.longitude) < epsilon
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Koordinat object oluşturma (validasyon ile)
59
+ */
60
+ static create(latitude: number, longitude: number): Coordinates | null {
61
+ if (!this.isValid(latitude, longitude)) {
62
+ return null;
63
+ }
64
+ return { latitude, longitude };
65
+ }
66
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Domain Layer - Distance Value Object
3
+ *
4
+ * Mesafe hesaplama işlemleri.
5
+ * Haversine formula implementation.
6
+ */
7
+
8
+ import { Coordinates, DistanceUnit } from "../../types/location.types";
9
+
10
+ export class DistanceVO {
11
+ private static readonly EARTH_RADIUS_KM = 6371;
12
+ private static readonly EARTH_RADIUS_MILES = 3959;
13
+ private static readonly EARTH_RADIUS_METERS = 6371000;
14
+
15
+ /**
16
+ * İki koordinat arasındaki mesafeyi hesapla (Haversine)
17
+ */
18
+ static calculate(
19
+ from: Coordinates,
20
+ to: Coordinates,
21
+ unit: DistanceUnit = "km"
22
+ ): number {
23
+ const lat1Rad = this.toRadians(from.latitude);
24
+ const lat2Rad = this.toRadians(to.latitude);
25
+ const deltaLatRad = this.toRadians(to.latitude - from.latitude);
26
+ const deltaLonRad = this.toRadians(to.longitude - from.longitude);
27
+
28
+ const a = this.calculateHaversineA(lat1Rad, lat2Rad, deltaLatRad, deltaLonRad);
29
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
30
+
31
+ return this.multiplyByRadius(c, unit);
32
+ }
33
+
34
+ private static toRadians(degrees: number): number {
35
+ return (degrees * Math.PI) / 180;
36
+ }
37
+
38
+ private static calculateHaversineA(
39
+ lat1Rad: number,
40
+ lat2Rad: number,
41
+ deltaLatRad: number,
42
+ deltaLonRad: number
43
+ ): number {
44
+ return (
45
+ Math.sin(deltaLatRad / 2) * Math.sin(deltaLatRad / 2) +
46
+ Math.cos(lat1Rad) *
47
+ Math.cos(lat2Rad) *
48
+ Math.sin(deltaLonRad / 2) *
49
+ Math.sin(deltaLonRad / 2)
50
+ );
51
+ }
52
+
53
+ private static multiplyByRadius(c: number, unit: DistanceUnit): number {
54
+ switch (unit) {
55
+ case "miles":
56
+ return this.EARTH_RADIUS_MILES * c;
57
+ case "meters":
58
+ return this.EARTH_RADIUS_METERS * c;
59
+ case "km":
60
+ default:
61
+ return this.EARTH_RADIUS_KM * c;
62
+ }
63
+ }
64
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,35 @@
1
+ // ============================================================
2
+ // Types
3
+ // ============================================================
1
4
  export * from "./types/location.types";
5
+
6
+ // ============================================================
7
+ // Domain Layer - Core business logic
8
+ // ============================================================
9
+ export * from "./domain/errors/LocationErrors";
10
+ export * from "./domain/value-objects/CoordinatesVO";
11
+ export * from "./domain/value-objects/DistanceVO";
12
+ export * from "./domain/services/PermissionService";
13
+
14
+ // ============================================================
15
+ // Application Layer - Ports (Interfaces)
16
+ // ============================================================
17
+ export * from "./application/ports/ILoggerPort";
18
+ export * from "./application/ports/ICachePort";
19
+ export * from "./application/ports/ILocationPort";
20
+
21
+ // ============================================================
22
+ // Infrastructure Layer - Implementations
23
+ // ============================================================
2
24
  export * from "./infrastructure/services/LocationService";
3
- export * from "./infrastructure/services/LocationWatcher";
4
- export * from "./infrastructure/utils/LocationUtils";
5
- export * from "./presentation/hooks/useLocation";
6
- export * from "./presentation/hooks/useLocationWatch";
25
+ export * from "./infrastructure/services/LocationWatcherService";
26
+ export * from "./infrastructure/services/LocationLoggerService";
27
+ export * from "./infrastructure/repositories/LocationCache.repository";
28
+ export * from "./infrastructure/utils/Accuracy.utils";
29
+ export * from "./infrastructure/utils/ObjectComparison.utils";
30
+
31
+ // ============================================================
32
+ // Presentation Layer - Hooks
33
+ // ============================================================
34
+ export * from "./presentation/hooks/useLocationHook";
35
+ export * from "./presentation/hooks/useLocationWatchHook";
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Infrastructure Layer - Location Cache Repository
3
+ *
4
+ * Storage repository wrapper implementing cache port.
5
+ * Handles cache expiration logic.
6
+ */
7
+
8
+ import { storageRepository, unwrap } from "@umituz/react-native-design-system/storage";
9
+ import { ICachePort, CachedLocationData } from "../../application/ports/ICachePort";
10
+
11
+ export class LocationCacheRepository implements ICachePort {
12
+ async get(key: string): Promise<CachedLocationData | null> {
13
+ try {
14
+ const result = await storageRepository.getItem<CachedLocationData | null>(key, null);
15
+ return unwrap(result, null);
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
21
+ async set(key: string, data: CachedLocationData): Promise<void> {
22
+ try {
23
+ await storageRepository.setItem(key, data);
24
+ } catch {
25
+ // Silent fail - cache write errors shouldn't block location fetching
26
+ }
27
+ }
28
+
29
+ async remove(key: string): Promise<void> {
30
+ try {
31
+ await storageRepository.removeItem(key);
32
+ } catch {
33
+ // Silent fail
34
+ }
35
+ }
36
+
37
+ async clear(): Promise<void> {
38
+ // Not implemented - cache clearing not needed for location
39
+ // Individual cache entries are removed when expired
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Cache utilities
45
+ */
46
+ export class CacheUtils {
47
+ /**
48
+ * Cache anahtarı oluştur
49
+ */
50
+ static generateKey(baseKey: string, withAddress: boolean): string {
51
+ const suffix = withAddress ? "_addr" : "";
52
+ return `location_cache_${baseKey}${suffix}`;
53
+ }
54
+
55
+ /**
56
+ * Cache yaşını hesapla (ms)
57
+ */
58
+ static getAge(cachedTimestamp: number): number {
59
+ return Date.now() - cachedTimestamp;
60
+ }
61
+
62
+ /**
63
+ * Cache expired mi?
64
+ */
65
+ static isExpired(cachedTimestamp: number, maxDuration: number): boolean {
66
+ return this.getAge(cachedTimestamp) > maxDuration;
67
+ }
68
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Infrastructure Layer - Location Logger Service
3
+ *
4
+ * Console-based logger implementation.
5
+ * Uses __DEV__ checks for production optimization.
6
+ */
7
+
8
+ import { ILoggerPort, LogLevel } from "../../application/ports/ILoggerPort";
9
+
10
+ export class LocationLogger implements ILoggerPort {
11
+ private readonly context: string;
12
+ private readonly enabled: boolean;
13
+
14
+ constructor(context: string, enabled = __DEV__) {
15
+ this.context = context;
16
+ this.enabled = enabled;
17
+ }
18
+
19
+ private log(level: LogLevel, message: string, ...args: unknown[]): void {
20
+ if (!this.enabled) return;
21
+
22
+ const timestamp = new Date().toISOString();
23
+ const prefix = `[${timestamp}] [${level}] [${this.context}]`;
24
+
25
+ switch (level) {
26
+ case LogLevel.DEBUG:
27
+ case LogLevel.INFO:
28
+ console.log(prefix, message, ...args);
29
+ break;
30
+ case LogLevel.WARN:
31
+ console.warn(prefix, message, ...args);
32
+ break;
33
+ case LogLevel.ERROR:
34
+ console.error(prefix, message, ...args);
35
+ break;
36
+ }
37
+ }
38
+
39
+ debug(message: string, context?: string): void {
40
+ const ctx = context || this.context;
41
+ this.log(LogLevel.DEBUG, message, `[${ctx}]`);
42
+ }
43
+
44
+ info(message: string, context?: string): void {
45
+ const ctx = context || this.context;
46
+ this.log(LogLevel.INFO, message, `[${ctx}]`);
47
+ }
48
+
49
+ warn(message: string, context?: string): void {
50
+ const ctx = context || this.context;
51
+ this.log(LogLevel.WARN, message, `[${ctx}]`);
52
+ }
53
+
54
+ error(message: string, error?: unknown, context?: string): void {
55
+ const ctx = context || this.context;
56
+ this.log(LogLevel.ERROR, message, `[${ctx}]`, error);
57
+ }
58
+ }